Search code examples
javaswingjbuttonexecuteswingworker

Repeated execution of SwingWorker


I'm writing an app that does some audio analysis (for use as a guitar tuner with the addition of chord estimation and some other stuff), but I'm having some trouble with the GUI. The first problem is that I have a button which, upon clicking should change its displayed text. When it is clicked, the text does not change, but the line of code that should change it is definitely reached.

Another thing is that I need a SwingWorker to execute repeatedly (restarting once it has finished) and update the GUI each time. As my code is at the moment, I have a while loop repeatedly executing my SwingWorker, but this is causing the GUI to become unresponsive since it is running in the EDT (presumably). What would be the best way to have the SwingWorker repeatedly execute? Should I just create a new thread to run the loop in, or something else?

Code for my inner ActionListener class is as follows:

private class TunerListener implements ActionListener {
    private boolean firstUpdate = true;
    private volatile boolean executing = false;

    private final SwingWorker<Void, Void> tunerWorker = new SwingWorker<Void, Void>() {

        @Override
        protected Void doInBackground() {
            model.update();
            return null;
        }

        @Override
        protected void done() {
            if (!this.isCancelled()) {
                prev.setText(model.getPrev());
                currentNote.setText(model.getNote());
                next.setText(model.getNext());
                frequency.setText(model.getFrequency());
                switch (model.getOffset()) {
                    case -2:
                        light_2.setIcon(onRed);
                        light_1.setIcon(off);
                        light0.setIcon(offBig);
                        light1.setIcon(off);
                        light2.setIcon(off);
                        break;
                    case -1:
                        light_2.setIcon(off);
                        light_1.setIcon(onRed);
                        light0.setIcon(offBig);
                        light1.setIcon(off);
                        light2.setIcon(off);
                        break;
                    case 0:
                        light_2.setIcon(off);
                        light_1.setIcon(off);
                        light0.setIcon(onGreen);
                        light1.setIcon(off);
                        light2.setIcon(off);
                        break;
                    case 1:
                        light_2.setIcon(off);
                        light_1.setIcon(off);
                        light0.setIcon(offBig);
                        light1.setIcon(onRed);
                        light2.setIcon(off);
                        break;
                    case 2:
                        light_2.setIcon(off);
                        light_1.setIcon(off);
                        light0.setIcon(offBig);
                        light1.setIcon(off);
                        light2.setIcon(onRed);
                        break;
                }
            }
        }

    };

    @Override
    public void actionPerformed(ActionEvent ae) {

        if (ae.getActionCommand().equals("tune")) {
            if (!executing) {
                tune.setText("Stop Tuning");
                executing = true;

                while (executing) {
                    tunerWorker.execute();
                    firstUpdate = false;
                }
                firstUpdate = true;
            } else {
                tune.setText("Start Tuning");
                executing = false;
                tunerWorker.cancel(true);
                firstUpdate = true;
            }
        }

    }
}

Edit: The button text issue seems to have been fixed now, but I still can't get this SwingWorker to work properly. I've tried removing the while loop entirely and having it re-execute itself from its done() method, but this doesn't really seem to help matters...


Solution

  • DISCLAIMER: Multithreading isn't my area of greatest expertise...

    Both of your problems are really the same thing, e.g. Your GUI is becoming unresponsive because the EDT is busy running the while loop. This means that it can't redraw the button with a new text value and it can't react to user input.

    Also, each instance of a SwingWorker can only be run once, so calling execute() several times in a loop only runs it once.

    I recommend creating a TunerWorker object and putting the loop inside that, then creating a new one whenever you need to start the loop. Like this:

    class TunerListener implements ActionListener {
    
    private TunerWorker tw = null;
    
    @Override
    public void actionPerformed(ActionEvent ae) {
    
        if (ae.getActionCommand().equals("tune")) {
            if (tw == null || tw.isDone()) {
                tune.setText("Stop Tuning");
                executing = true;
    
                tw = new TunerWorker();
                tw.execute();
    
            } else {
                tune.setText("Start Tuning");
                executing = false;
                tw.cancel(true);
                }
            }
        }
    }
    
    final class TunerWorker extends SwingWorker<Void, Void> {
    
        @Override
        protected Void doInBackground() {
            while (!this.isCancelled()) {
                model.update();
            }        
            return null;
        }    
    
        @Override
        protected void done() {
            if (!this.isCancelled()) {
                //Removed this code to make the example prettier...
            }
        }
    }
    

    Oh, and I wasn't sure what you were trying to do with firstUpdate so I took it out of the example. Hopefully it won't be too hard to work out how to put it back in.

    EDIT: Whoops, that code didn't actually work. Should be fixed now.