简体   繁体   中英

How do I run Swing UI on two threads

I have a scenario where I need my swing UI to run on two different threads. I have a laptop where I will my application will run. There is a Button on clicking which an presentation should start at the other Screen that is attached to my laptop.

Now I have made a class presentation which is extending SwingWorker and reads the images from a folder and displays it on screen.

class Presenatation extends SwingWorker<Integer, Integer> {

    @Override
    protected Integer doInBackground() throws Exception {
            SwingUtilities.invokeLater(new Runnable() {
                public void run() {
                    start(outputFolder, screenVO);/*Creates a JFrame to be displayed
on new screen and sets a JPanel to it. Reads the file images sets it into 
JLabels every 2 seconds and updates it to Japnel*/
                }
            });
            return null;
        }

Inside my start method I have the code to read images and show them on the UI

What I feel is this approach is wrong since my SwingWorker shouldn't be calling invokeLater in doInBackground()

From what little knowledge I have, it should be something like this:

@Override
protected Void doInBackground() throws Exception
{

    return null;
}

@Override
protected void process(List<Integer> chunks
{

} 

I am not able to decide which part should be placed where ?

I have the following things to do :

  • Start a new Frame to be displayed on a new screen
  • Load Images into the frame every 2 seconds reading the image from a folder
  • Extending Presentation class to SwingWorker, is this approach Correct ? Because externally I have an Executor object in whose exec() I am passing the object of Presentation

Please help me !

Indeed, you should not call invokeLater from doInbackground, nor spawning a new thread. Note that SwingWorker is a Runnable, so it can be submitted to an Executor.

But you have to call the publish method every time you have a new image ready to display. Behind the scene the SwingWorker will invoke its process method on the Event Dispatch thread.

So, you have to override process to do the actual widgets update.

Take this answer with a grain of salt because many will disagree and say this is a terrible practice. However, no one, so far as I've seen, can precisely say WHY this is bad. So, until then, I maintain my opinion:

I think it is perfectly fine to call invokeLater() and invokeAndWait() within doInBackground() .

There, I said it.

I asked basically the same question last month and I since then I've been experimenting with replacing process() and publish() with invokeLater() . In my experiments, I've not run into any threading or synchronization issues. And the approach is a lot easier than publish() and process() .

Why do I say this?

First, by calling invokeLater , you are throwing whatever code you run to the EDT. So, there's no logical reason why that code should break.

Second, process() and publish() were written with very, very specific goals in mind (ie, to provide a way for you to send tabular results so you can update your JTable or JList in real-time). I've never, not once, used process() or publish() in the way it was obviously written to be used (and I work with a lot of tabular data). In order to get publish() and process() to do what I want (90% of the time update an indeterminate JProgressBar and 9% of the time update a determinate JProgressBar ), I usually end up writing some hackish way of publishing and processing my data. It's confusing, difficult to read, and difficult to manage change requests.

However, invokeLater() from within doInBackground() is easy to read and, again, I haven't heard anyone say why it would be unsafe to do. And, as in the case presented in the question I linked above, if you need to pause execution for user feedback, I don't see there being any other option than to invokeAndWait() .

My honest opinion is that publish() and process() weren't very well written. SwingWorker is amazing once you learn the nuances, but those methods do not cover the needs of most people using SwingWorker and their need to update the user about progress. At least, not in my experience.

EDIT: Specifically regarding your situation, what I would do is:

  1. Create a constructor for your SwingWorker and initialize a dialog there, but make it a class member of the SwingWorker so you can update it. For example:

    class Task extends SwingWorker {

     IndeterminateLoadingDialog ild; public Task _Task() { JDialog dialog = new JDialog(// parent frame); ild = new IndeterminateLoadingDialog(this); dialog.add(ild); dialog.pack(); dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE); dialog.setLocationRelativeTo(dialog.getParent()); dialog.setVisible(true); } 

My IndeterminateLoadingDialog class is a JPanel that just has a JProgressBar and a JLabel . I can change the text through a method that interfaces with the JLabel .

Anyways, I always initialize whatever GUI components I need for the SwingWorker in the constructor. Just make sure new Task().execute() is called from the EDT.

If I need to update my IndeterminateLoadingDialog , I will just use invokeLater() to change the text directly.

Further, I close the dialog in the done() method.

Hope this helps you.

Some background

Swing (and all other GUI frameworks) are not multi-threaded (because of a long list of valid reasons).

Therefore a good rule of thumb is to create and operate any GUI components from the EDT thread only . There are edge cases (such as creating a new JFrame from a thread) which might work, but basically it is a bad idea to do anything with the GUI from a different thread than EDT.

You not only should but have to call invokeLater or invokeAndWait from any non-EDT thread. This is the only way to ensure that your thread is getting suspended, and the submitted Runnable is getting executed from the context of the EDT thread.

Some solutions

Basically, you definitely not need two EDT threads. In fact it is a bad idea, you have one keyboard, and one mouse creating one set of UI events, therefore two EDT threads make no use.

In fact, you don't even need a swingworker to switch pictures on the second display. SwingWorkers are great for off-loading long-running non-gui operations from the EDT thread (ie executing a database operation which takes 20 seconds to complete). In your case, loading a new picture is not a rocket science :)

Do the followings:

  • discard all swingworkers and other stuff
  • when you press the button, open the new presentation window
  • in the presentation window create a timer which fires in every 2 seconds
  • when the timer fires, load the new picture and throw that to the window

It is quite simple in real. Consider this example:

package a;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.JButton;
import javax.swing.JFrame;

public class PresentationStart extends JFrame {

    public static void main(final String[] args) {
        new PresentationStart();
    }

    public PresentationStart() {
        super("Start here");
        final JButton button=new JButton("Start");
        button.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(final ActionEvent e) {
                new PresentationView();
            }
        });
        add(button);
        pack();
        setVisible(true);
    }
}

And the viewer:

package a;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;

import javax.imageio.ImageIO;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.Timer;

public class PresentationView extends JFrame {

    public PresentationView() {
        super("View");
        final JLabel picture=new JLabel("Picture comes here");
        add(picture);
        pack();
        setVisible(true);

        final List<String> pictures=new ArrayList<String>();
        pictures.add("http://storage3d.com/storage/2008.10/49d7c6aeed760176755a7570b55db587.jpg");
        pictures.add("http://storage3d.com/storage/2008.10/49d7c6aeed760176755a7570b55db587.jpg");
        pictures.add("http://www.alragdkw.com/wp-content/uploads/2016/01/fruit-Banans.jpg");

        final Timer timer=new Timer(2000,new ActionListener() {
            int index=0;

            @Override
            public void actionPerformed(final ActionEvent e) {
                // Load a new picture
                try {
                    picture.setIcon(new ImageIcon(ImageIO.read(new URL(pictures.get(index)))));
                } catch (final Exception ex) {
                    ex.printStackTrace();
                }
                index++;
                if (index>=pictures.size()) {
                    index=0;
                }
            }
        });
        timer.start();
    }
}

Make your presentation as a standalone java program, and start it with Runtime.exec(). It will create a separate window.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM