2

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();
    }
}
Eng.Fouad
  • 115,165
  • 71
  • 313
  • 417
Darbininkai Broliai
  • 159
  • 1
  • 1
  • 13
  • Have you tried adding a Debug statement in your first Runnable to verify? As written, it's just a straight up function call, nothing special about it... – Krease Dec 26 '12 at 20:12

2 Answers2

4

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();
    }
};
Krease
  • 15,805
  • 8
  • 54
  • 86
  • So this is the correct solution for my smaller case, but for my bigger case, made from a window with several buttons and then each button leading to the game (in different languages), doesn't work either with runnable.run() or SwingUtilities.InvokeLater(runnable); – Darbininkai Broliai Dec 27 '12 at 09:12
  • Try to step through it in a debugger - the statements in your Runnables will get executed (just add breakpoints or debug statements to prove it); it's the correctness of the statements within them that generally matter. A good debugger will help you see the code flow more clearly and help you focus on what the state is at various points and what might be wrong. – Krease Dec 27 '12 at 16:11
3

You should first encapsulate the Runnable object within a Thread object and then start your thread by invoking start(). For example:

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


EDIT:

You should make sure to invoke your Runnable's from the EDT. For example:

SwingUtilties.invokeLater(r);

Or you might use SwingWorker for dealing with intensive operations that are involved with the swing code. See this answer to understand how SwingWorker works.

Community
  • 1
  • 1
Eng.Fouad
  • 115,165
  • 71
  • 313
  • 417
  • Could you give a short example of what this would look like? – Darbininkai Broliai Dec 26 '12 at 20:06
  • Changed your answer, the threads talk only serves to obscure the valid point. – user268396 Dec 26 '12 at 20:14
  • While a valid point, I'm not sure how this addresses the question of the function 'not running'. Using invokeLater would also change the intended execution order (though no real effect in the example given). – Krease Dec 26 '12 at 20:20
  • So I shouldnt use the thread then? And what if order doesn't matter for me - i simply want to run the selected runnable from the initial screen. – Darbininkai Broliai Dec 26 '12 at 20:27
  • @DarbininkaiBroliai In your code above, you are creating 2 Runnable objects and use them as normal objects. When you invoke `runTwo.run()`, it will be running on the main thread. But with `SwingUtilties.invokeLater(r);`, you schedule it to run on the Event-Driven Thread (EDT). However, @Chris is right. That seems not to be the problem, your `runone.run()` should be invoked on clicking on the button `button1`. – Eng.Fouad Dec 26 '12 at 20:36
  • See my answer for why your sample does not work and how to fix it. – Krease Dec 26 '12 at 21:13