Как запустить Swing UI в двух потоках

У меня есть сценарий, в котором мне нужно, чтобы мой пользовательский интерфейс Swing работал в двух разных потоках. У меня есть ноутбук, на котором будет работать мое приложение. Есть кнопка, при нажатии которой презентация должна начинаться на другом экране, подключенном к моему ноутбуку.

Теперь я сделал презентацию класса, которая расширяет 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;
        }

Внутри моего метода запуска у меня есть код для чтения изображений и отображения их в пользовательском интерфейсе.

Я считаю, что этот подход неверен, поскольку мой SwingWorker не должен вызывать invokeLater в doInBackground ()

Судя по тому небольшому количеству знаний, которые у меня есть, должно получиться что-то вроде этого:

@Override
protected Void doInBackground() throws Exception
{

    return null;
}

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

} 

Я не могу решить, какую часть где разместить?

Мне нужно сделать следующее:

  • Начать новый фрейм для отображения на новом экране
  • Загружать изображения в кадр каждые 2 секунды считывая изображение из папки
  • Правильный ли этот подход - расширение класса Presentation до SwingWorker? Поскольку внешне у меня есть объект Executor, в exec () которого я передаю объект Presentation

Помогите, пожалуйста !


person ItachiUchiha    schedule 28.01.2014    source источник
comment
В EDT должны использоваться компоненты Swing. Это включает в себя создание нового JFrame и его отображение. Чтение файлов должно производиться вне EDT. Но отображение изображений в кадре (например, путем добавления или изменения в JLabels) должно выполняться в EDT. Просто: задействован компонент Swing или его модель == ›EDT.   -  person JB Nizet    schedule 28.01.2014
comment
даже я знаю об этом, но поскольку я новичок в свинге, я не могу решить, какую часть кода и куда поместить. Поскольку я обязан запустить презентацию в новом потоке, мне нужно расширить его с помощью SwingWorker, или это неверно?   -  person ItachiUchiha    schedule 28.01.2014
comment
Сколько изображений вы хотите показывать каждые две секунды? Это фиксированное или переменное число? Вы пытаетесь сделать презентацию, такую ​​как MS PowerPoint или LibreOffice Impress?   -  person dic19    schedule 28.01.2014
comment
Представляет собой презентацию изображений, т.е. все изображения из папки считываются и отображаются на экране с интервалом в 2 секунды каждое.   -  person ItachiUchiha    schedule 28.01.2014
comment
Вы не можете запустить поворотную рамку в новом потоке. Swing работает в EDT, а EDT только один. Нет проблем с одновременным использованием двух или более кадров. Но всеми ими нужно управлять из EDT. В javadoc SwingWorker четко объясняется, какой метод работает в каком потоке и как его следует использовать. Прочтите это.   -  person JB Nizet    schedule 28.01.2014


Ответы (4)


В самом деле, вы не должны вызывать invokeLater из doInbackground или создавать новый поток. Обратите внимание, что SwingWorker является Runnable, поэтому его можно передать Исполнителю.

Но вы должны вызывать метод публикации каждый раз, когда у вас есть новое изображение, готовое к отображению. За сценой SwingWorker вызовет свой метод процесса в потоке отправки событий.

Итак, вам нужно переопределить процесс, чтобы выполнить фактическое обновление виджетов.

person PeeWee2201    schedule 28.01.2014

Отнеситесь к этому ответу с недоверием, потому что многие не согласятся и скажут, что это ужасная практика. Однако, насколько я видел, никто не может точно сказать, ПОЧЕМУ это плохо. Итак, до тех пор я придерживаюсь своего мнения:

Думаю, вполне нормально вызывать invokeLater() и invokeAndWait() в doInBackground().

Я сказал это.

В прошлом месяце я задал тот же вопрос, и с тех пор я экспериментирую с заменой process() и publish() с invokeLater(). В своих экспериментах я не сталкивался с какими-либо проблемами с потоками или синхронизацией. И этот подход намного проще, чем publish() и process().

Почему я это говорю?

Во-первых, вызывая invokeLater, вы отправляете код, который запускаете, в EDT. Итак, нет никакой логической причины, по которой этот код должен сломаться.

Во-вторых, process() и publish() были написаны с очень, очень конкретными целями (т. Е. Предоставить вам способ отправлять табличные результаты, чтобы вы могли обновлять свои JTable или JList в режиме реального времени). Я ни разу, ни разу не использовал process() или publish() так, как это было очевидно написано для использования (и я работаю с большим количеством табличных данных). Чтобы заставить publish() и process() делать то, что я хочу (90% времени обновляют неопределенное JProgressBar и 9% времени обновляют определенное JProgressBar), я обычно пишу какой-нибудь хакерский способ публикации и обработки моих данных. Это сбивает с толку, трудно читать и сложно управлять запросами на изменение.

Однако 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);
    }
    

Мой IndeterminateLoadingDialog класс - это JPanel, в котором есть только JProgressBar и JLabel. Я могу изменить текст с помощью метода, который взаимодействует с JLabel.

В любом случае, я всегда инициализирую любые компоненты графического интерфейса, которые мне нужны для SwingWorker в конструкторе. Просто убедитесь, что new Task().execute() вызывается из EDT.

Если мне нужно обновить свой IndeterminateLoadingDialog, я просто использую invokeLater(), чтобы напрямую изменить текст.

Далее закрываю диалог в методе done().

Надеюсь, это вам поможет.

person ryvantage    schedule 29.01.2014

Некоторая предыстория

Swing (и все другие GUI-фреймворки) не являются многопоточными (из-за длинного списка уважительных причин).

Поэтому хорошее практическое правило - создавать и управлять любыми компонентами графического интерфейса пользователя только из потока EDT. Существуют крайние случаи (например, создание нового JFrame из потока), которые могут работать, но в основном делать что-либо с графическим интерфейсом пользователя из другого потока, чем EDT, - плохая идея.

Вы не только должны, но и должны вызывать invokeLater или invokeAndWait из любого потока, не относящегося к EDT. Это единственный способ гарантировать, что ваш поток приостанавливается, а отправленный Runnable запускается из контекста потока EDT.

Некоторые решения

В принципе, вам точно не нужны два потока EDT. На самом деле это плохая идея, у вас есть одна клавиатура и одна мышь, создающие один набор событий пользовательского интерфейса, поэтому два потока EDT бесполезны.

На самом деле, вам даже не нужен свингерверк, чтобы переключать картинки на втором дисплее. SwingWorkers отлично подходят для выгрузки из потока EDT длительно выполняемых операций без графического интерфейса (т. Е. Выполнения операции с базой данных, которая занимает 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();
    }
}
person Gee Bee    schedule 24.02.2016

Сделайте свою презентацию как отдельную Java-программу и запустите ее с помощью Runtime.exec (). Это создаст отдельное окно.

person Alexei Kaigorodov    schedule 28.01.2014
comment
Он должен быть на отдельном экране, который подключен к ноутбуку, и экран определяется пользователем во время выполнения этого приложения. Так же как и папка, из которой надо читать изображения! - person ItachiUchiha; 28.01.2014