Search code examples
javaswinguser-interfacerunnableswingworker

Java running a Runnable from inside another Runnable won't work


why doesnt the following code work? Basically, this is a simplified version of a more difficult program in which I am trying to make an runnable initial screen with selections that would then have buttons that link to different runnables, but this doesn't run as I expected it to.

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

Solution

  • There's nothing special about Runnable that would prevent this from working. As your code sample stands, the following are equivalent:

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

    and

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

    Taking your code and adding a System.out.println debug statement inside runone.run shows that it is in fact being executed.

    I assume your code sample is meant to be a simplified demo of your issue; you may want to look into getting it to do what you want as a 'plain function' first (my second example above, where the Runnables are combined), then separating into distinct Runnables afterwards.

    Edit - to get your example to work, the thing to remember is that JFrame uses a contentPane to host its children - frame.add exists as a convenience to add to the contentPane (based on javadoc for JFrame), but removeAll does not do this (based on me playing with it just now). Also, calling validate after the button is added will properly relayout the subcomponents again to get your second button to appear.

    Replace your definition of runone with this one, and your sample will work:

    runone = new Runnable() {
        @Override
        public void run() {
            frame.getContentPane().removeAll();
            frame.add(button2);
            frame.validate();
        }
    };