如何在两个线程上运行Swing UI

时间:2014-01-28 13:20:05

标签: java multithreading swing swingworker swingutilities

我有一个场景,我需要我的swing UI在两个不同的线程上运行。我有一台笔记本电脑,我将运行我的应用程序。单击时会出现一个按钮,演示文稿应从连接到我的笔记本电脑的另一个屏幕开始。

现在我做了一个类演示,它扩展了SwingWorker并从文件夹中读取图像并将其显示在屏幕上。

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;
        }

在我的start方法中,我有代码来读取图像并在UI上显示它们

我觉得这种方法是错误的,因为我的SwingWorker不应该在doInBackground()中调用invokeLater

从我的小知识,它应该是这样的:

@Override
protected Void doInBackground() throws Exception
{

    return null;
}

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

} 

我无法决定哪个部分应放在哪里?

我有以下事情要做:

  • 开始新帧以在新屏幕上显示
  • 每2秒钟从文件夹中读取图像
  • 将图像加载到画面中
  • 将Presentation类扩展到SwingWorker,这种方法是否正确?因为外部我有一个Executor对象,其exec()我传递的是Presentation
  • 的对象

请帮助我!

4 个答案:

答案 0 :(得分:0)

实际上,您不应该从doInbackground调用invokeLater,也不应该生成新线程。请注意,SwingWorker是一个Runnable,因此可以将其提交给Executor。

但是每次准备好显示新图像时都必须调用发布方法。在场景后面,SwingWorker将在Event Dispatch线程上调用其进程方法。

因此,您必须覆盖进程以执行实际的小部件更新。

答案 1 :(得分:0)

这个答案带着一丝盐,因为许多人不同意并说这是一种可怕的做法。然而,就我所见,没有人可以准确地说出为什么这是坏事。所以,在那之前,我坚持自己的观点:

我认为在invokeLater() 中拨打invokeAndWait()doInBackground()是完全可以的。

在那里,我说了。

我上个月问了basically the same question,从那时起我一直在尝试用process()替换publish()invokeLater()。在我的实验中,我没有遇到任何线程或同步问题。这种方法比publish()process()容易得多。

我为什么这么说?

首先,通过调用invokeLater,您将向EDT投放的任何代码。因此,没有合理的理由说明代码应该破解。

其次,process()publish()的编写时考虑了非常非常具体的目标(即,为您提供一种发送表格式结果的方式,以便您可以更新JTableJList实时)我从来没有,不是一次,使用process()publish()的方式显然是(我使用了很多表格数据)。为了让publish()process()做我想做的事(90%的时间更新不确定的JProgressBar,9%的时间更新确定的JProgressBar),我通常最终会编写一些发布和处理数据的hackish方式。它令人困惑,难以阅读,难以管理变更请求。

然而,invokeLater()内的doInBackground()很容易阅读,而且我再也听不到有人说为什么这样做会不安全。而且,正如我在上面链接的问题中提到的情况一样,如果您需要暂停执行以获得用户反馈,我认为除了invokeAndWait()之外没有任何其他选项。

我的诚实意见是publish()process()写得不是很好。一旦你了解了细微差别,SwingWorker就会很棒,但这些方法并不能满足大多数人使用SwingWorker的需求以及他们需要更新用户的进度。至少,不是我的经验。

编辑:特别是关于你的情况,我会做的是:

  1. SwingWorker创建一个构造函数并在那里初始化一个对话框,但要使其成为SwingWorker的类成员,以便您可以更新它。例如:

    class Task扩展了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);
    }
    
  2. 我的IndeterminateLoadingDialog班级是JPanel,只有JProgressBarJLabel。我可以通过与JLabel接口的方法更改文本。

    无论如何,我总是初始化构造函数中SwingWorker所需的任何GUI组件。只需确保从EDT调用new Task().execute()

    如果我需要更新IndeterminateLoadingDialog,我只会使用invokeLater()直接更改文字。

    此外,我使用done()方法关闭对话框。

    希望这会对你有所帮助。

答案 2 :(得分:0)

一些背景

Swing(和所有其他GUI框架)不是多线程的(因为有很多有效的原因)。

因此,一个好的经验法则是从EDT主题 创建和操作任何GUI组件。有一些边缘情况(例如从一个线程创建一个新的JFrame)可能会起作用,但基本上从与EDT不同的线程对GUI做任何事情都是一个坏主意。

您不仅应该,而且必须从任何非EDT线程调用invokeLater或invokeAndWait。这是确保您的线程被挂起的唯一方法,并且提交的Runnable将从EDT线程的上下文执行。

一些解决方案

基本上,你绝对不需要两个EDT线程。事实上,这是一个坏主意,你有一个键盘,一个鼠标创建一组UI事件,因此两个EDT线程没有用。

事实上,你甚至不需要一个swingworker来切换第二个显示器上的图片。 SwingWorkers非常适合从EDT线程卸载长时间运行的非gui操作(即执行需要20秒才能完成的数据库操作)。在你的情况下,加载一张新图片并不是火箭科学:)

执行以下操作:

  • 丢弃所有摇摆工作者和其他东西
  • 按下按钮时,打开新的演示文稿窗口
  • 在演示文稿窗口中创建一个每2秒触发一次的计时器
  • 当计时器触发时,加载新图片并将其扔到窗口

实际上非常简单。考虑这个例子:

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);
    }
}

观众:

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();
    }
}

答案 3 :(得分:-1)

将您的演示文稿作为独立的Java程序,并使用Runtime.exec()启动它。它将创建一个单独的窗口。