1

I have a program that displays a picture of a coin. When the user clicks the coin the coin changes from heads to tails and vice versa. That works fine. The problem arises when I want to have a button that flips the coin a random number of times (In this case between 3 and 10 times inclusive). The method used to change the image icon:

flip.addActionListener(new ActionListener(){
        @Override
        public void actionPerformed(ActionEvent e1) {
            playerCoinState = coinState;
            System.out.println("Clicked");
            int flips = (new Random().nextInt(8)) + 3;
                    for(int i = 0; i < flips; i++){
                        try{
                            Thread.sleep(1000);
                        }catch(InterruptedException e2){
                            System.exit(1);
                        }
                        System.out.println("Auto Flipped");
                        changeFace();
                    }
        }
    });

And this is the method used to change the ImageIcon of the JLabel coin:

private void changeFace(){
    System.out.println("Changing...");
    switch(coinState){
    case 0:
        System.out.println("Coin State 0");
        try {
            coin.setIcon(new ImageIcon(ImageIO.read(new File("res/Heads.png"))));
        } catch (IOException e) {
            e.printStackTrace();
        }
        coinState = 1;
        break;
    case 1:
        System.out.println("Coin State 1");
        try {
            coin.setIcon(new ImageIcon(ImageIO.read(new File("res/Tails.png"))));
        } catch (IOException e) {
            e.printStackTrace();
        }
        coinState = 0;
        break;
    }
}

The JLabel coin is initialised as:

coin = new JLabel(new ImageIcon("res/Tails.png"));

coinState represents the value of the coin. 0 for heads, 1 for tails. playerCoinState is used to keep track of the coin state the player has selected before the coin is to be flipped by the computer the random amount of times.

Matty2532
  • 50
  • 10
  • I should add that the println statements I have added throughout the program are all triggered when expected. The flow of the program is fine. It's just that the coin.setIcon doesn't change the image in the gui. – Matty2532 Mar 10 '17 at 22:03
  • Please edit your question to update details and include a [mcve] that exhibits the problem you describe. In your example, access posted images via `URL`, as shown [here](http://stackoverflow.com/a/10862262/230513); use synthetic images as shown [here](http://stackoverflow.com/a/15982915/230513); or use `UIManager` icons, as shown [here](http://stackoverflow.com/a/12228640/230513). Construct and manipulate Swing GUI objects _only_ on the [event dispatch thread](http://docs.oracle.com/javase/tutorial/uiswing/concurrency/initial.html). – trashgod Mar 10 '17 at 22:16
  • Your blocking the event dispatching thread preventing the ui from been update until your for-loop finishes – MadProgrammer Mar 10 '17 at 22:32

3 Answers3

1
coin = new JLabel(new ImageIcon((ImageIO.read(new File("path"))));

Use this instead of coin.setIcon(new ImageIcon(ImageIO.read(new File("res/Tails.png")))); So you are creating the coin label new every time. If you are working with eclispe and windowbuilder, you can just use the tools on the sidebar.

Timf2000
  • 29
  • 4
  • That didn't work. I didn't think it would. The reason I think it isn't working is because when you set coin = new JLabel you are creating a new instance of the JLabel class. You would have to add this to the JPanel before the window is open in order for it to display which wouldn't work as the window needs to be open for the user to change it or to click the flip button. – Matty2532 Mar 10 '17 at 22:21
  • What about using a JPanel instead of a JLabel. With the paintComponent methode, you can draw, load Images and write. – Timf2000 Mar 10 '17 at 22:25
  • If the JLabel isn't updating, then creating a new one or using a JPanel isn't going to solve the underlying problem – MadProgrammer Mar 10 '17 at 22:33
  • And break the single thread rules of the API while your at it – MadProgrammer Mar 10 '17 at 22:45
1

This...

               for(int i = 0; i < flips; i++){
                    try{
                        Thread.sleep(1000);
                    }catch(InterruptedException e2){
                        System.exit(1);
                    }
                    System.out.println("Auto Flipped");
                    changeFace();
                }

Is blocking the event dispatching thread, preventing ui from been updated until after the method exits

You should try using a Swing Timer instead, which acts as a pseudo loop, but waits in the background for a prescribed period of time before triggering a tick within the context of the EDT, making it safe to update the UI from

MadProgrammer
  • 343,457
  • 22
  • 230
  • 366
  • This is the solution I have come across after doing some research. I'm struggling to understand how Timers work though and how to get the timer to run the number of times specified by the flips integer in my code :/ – Matty2532 Mar 10 '17 at 22:47
  • A timer will keep repeating (by default), you need to reset a counter before you start the timer and increment the counter each time it ticks, when you reach your desired count, you stop the timer. Basically, think of a timer as a type of "free wheeling" (think while(true)) loop, each tick of the timer is a run through the loop, you increment your counter and update the state of ui as required – MadProgrammer Mar 10 '17 at 22:51
  • [For example](http://stackoverflow.com/questions/20864431/java-swing-timer-break/20864524#20864524) – MadProgrammer Mar 10 '17 at 22:53
  • [For example](http://stackoverflow.com/questions/20296339/is-there-a-method-that-generates-a-timer-in-a-jframe/20297103#20297103) – MadProgrammer Mar 10 '17 at 22:57
0

MadProgrammer helped me to solve this.
The new and improved ActionListener for the button is:

flip.addActionListener(new ActionListener() {
        @Override
        public void actionPerformed(ActionEvent e1) {
            playerCoinState = coinState;
            int flips = (new Random().nextInt(10) + 5);
            Timer timer = new Timer(400, new ActionListener(){
            int counter = 0;
                @Override
                public void actionPerformed(ActionEvent e) {
                    if(counter == flips){
                        ((Timer)e.getSource()).stop();
                    }
                    changeFace();
                    counter++;
                }

            });
            timer.start();
        }
    }); 

Thanks again MadProgrammer =)

Matty2532
  • 50
  • 10