Java, запускающая Runnable из другого Runnable, не будет работать

почему следующий код не работает? По сути, это упрощенная версия более сложной программы, в которой я пытаюсь создать исполняемый начальный экран с выборками, которые затем будут иметь кнопки, ссылающиеся на разные исполняемые элементы, но это не работает так, как я ожидал.

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.*;

public class Runnables {
    static Runnable runone;
    static Runnable runtwo;
    static JFrame frame = new JFrame();
    static JButton button1 = new JButton("Initial screen");
    static JButton button2 = new JButton("After button click screen");

    public static void main(String[] args) {
        runone = new Runnable() {
            @Override
            public void run() {
                frame.removeAll();
                frame.revalidate();
                frame.repaint();
                frame.add(button2);
            }

        };
        runtwo = new Runnable() {
            @Override
            public void run() {
                frame.setSize(800, 600);
                frame.setVisible(true);
                button1.addActionListener(new ActionListener() {
                    @Override
                    public void actionPerformed(ActionEvent arg0) {
                        runone.run();
                        System.out
                                .println("This is performed, but the button doesnt change");
                    }
                });
                frame.add(button1);
            }
        };
        runtwo.run();
    }
}

person Darbininkai Broliai    schedule 26.12.2012    source источник
comment
Вы пытались добавить оператор Debug в свой первый Runnable для проверки? Как написано, это просто прямой вызов функции, ничего особенного в этом нет...   -  person Krease    schedule 27.12.2012


Ответы (2)


В Runnable нет ничего особенного, что мешало бы этому работать. Как видно из вашего примера кода, следующее эквивалентно:

public void actionPerformed(ActionEvent arg0) {
    runone.run();
    System.out.println("This is performed, but the button doesnt change");
}

и

public void actionPerformed(ActionEvent arg0) {
    frame.removeAll();
    frame.revalidate();
    frame.repaint();
    frame.add(button2);
    System.out.println("This is performed, but the button doesnt change");
}

Взяв ваш код и добавив оператор отладки System.out.println внутрь runone.run, вы увидите, что он действительно выполняется.

Я предполагаю, что ваш пример кода предназначен для упрощенной демонстрации вашей проблемы; вы можете сначала попытаться заставить его делать то, что вы хотите, как «простую функцию» (мой второй пример выше, где Runnables объединены), а затем разделить на отдельные Runnables.

Редактировать. Чтобы ваш пример работал, нужно помнить, что JFrame использует contentPane для размещения своих дочерних элементов. frame.add существует для удобства добавления в contentPane (на основе в javadoc для JFrame), но removeAll делает не делайте этого (на основании того, что я только что играл с этим). Кроме того, вызов validate после добавления кнопки снова правильно перенесет подкомпоненты, чтобы появилась ваша вторая кнопка.

Замените свое определение runone на это, и ваш образец будет работать:

runone = new Runnable() {
    @Override
    public void run() {
        frame.getContentPane().removeAll();
        frame.add(button2);
        frame.validate();
    }
};
person Krease    schedule 26.12.2012
comment
Так что это правильное решение для моего меньшего случая, но для моего большего случая, сделанного из окна с несколькими кнопками, а затем каждая кнопка, ведущая к игре (на разных языках), не работает ни с runnable.run(), ни с SwingUtilities.InvokeLater(запускаемый); - person Darbininkai Broliai; 27.12.2012
comment
Попробуйте выполнить это в отладчике — операторы в ваших Runnables будут выполнены (просто добавьте точки останова или операторы отладки, чтобы доказать это); обычно важна правильность утверждений внутри них. Хороший отладчик поможет вам более четко увидеть поток кода и поможет вам сосредоточиться на состоянии в различных точках и на том, что может быть неправильным. - person Krease; 27.12.2012

Вы должны сначала инкапсулировать объект Runnable в объект Thread, а затем запустить поток, вызвав start(). Например:

Runnable r = ...;
Thread thread = new Thread(r);
thread.start();


ИЗМЕНИТЬ:

Вы должны обязательно вызывать свои Runnable из EDT. Например:

SwingUtilties.invokeLater(r);

Или вы можете использовать SwingWorker для работы с интенсивными операциями, связанными с кодом свинга. См. этот ответ, чтобы понять, как работает SwingWorker.

person Eng.Fouad    schedule 26.12.2012
comment
Не могли бы вы привести краткий пример того, как это будет выглядеть? - person Darbininkai Broliai; 27.12.2012
comment
Изменил ваш ответ, разговоры в темах служат только для того, чтобы скрыть действительную точку зрения. - person user268396; 27.12.2012
comment
Хотя это и правильно, я не уверен, как это решает вопрос о том, что функция «не работает». Использование invokeLater также изменило бы предполагаемый порядок выполнения (хотя в приведенном примере это не дало бы реального эффекта). - person Krease; 27.12.2012
comment
Так я не должен использовать нить тогда? А что, если порядок для меня не имеет значения - я просто хочу запустить выбранный исполняемый файл с начального экрана. - person Darbininkai Broliai; 27.12.2012
comment
@DarbininkaiBroliai В приведенном выше коде вы создаете 2 объекта Runnable и используете их как обычные объекты. Когда вы вызываете runTwo.run(), он будет работать в основном потоке. Но с SwingUtilties.invokeLater(r); вы планируете его запуск в потоке, управляемом событиями (EDT). Однако @Chris прав. Кажется, это не проблема, ваш runone.run() должен вызываться при нажатии на кнопку button1. - person Eng.Fouad; 27.12.2012
comment
Смотрите мой ответ, почему ваш образец не работает и как это исправить. - person Krease; 27.12.2012