this is a small example that, i hope reflects the larger project that i am working on.
i am making a board game that uses a combination of mouse listener and jbutton action listener for the player to move. when it is time for the computer to take its turn, however, i cannot seem to get the program to update the graphics in between moves. it will do so when the computer has done all of its moves and kicks control back to the player. i am not sure if it is the adding of jbuttons to my jframe that is causing it to repaint or not, but in this small example, it still repaints after the loop is complete even though there is no altering of the jframe or any of the components in it.

i have tried using revalidate() as well and it does not seem to have any effect on it.

here is the code, any insight would be greatly appreciated.
thank you

import javax.swing.*;
import java.awt.*;
import java.io.*;
import java.awt.event.*;
import java.util.*;

public class testTwo extends JPanel{
  
  Image ladyBug;
  Image iLB;
  cmonDoIt testPanel;
  JScrollPane testScroll;
  JFrame gameFrameTwo;
  
  
  public static void gameFrameTwo(){
    JFrame gameFrameTwo = new JFrame("test");
    gameFrameTwo.setPreferredSize(new Dimension(400, 400));
    gameFrameTwo.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    gameFrameTwo.setResizable(false);
    testTwo content = new testTwo();
    content.setOpaque(true);
    gameFrameTwo.setContentPane(content);
    gameFrameTwo.pack();
    gameFrameTwo.setVisible(true);
  }
  
  public testTwo(){
    testPanel = new cmonDoIt();
    
    add(testPanel);
    testPanel.addMouseListener(testPanel);
  }
  
  public static void main(String[] args) {  
    javax.swing.SwingUtilities.invokeLater(new Runnable() {
      public void run() {
        gameFrameTwo(); 
      }
    });
  }
  
  
  class cmonDoIt extends JPanel implements MouseListener{
    
    Image currentView = Toolkit.getDefaultToolkit().getImage("images/LadyBug.GIF");
    Image yellow = Toolkit.getDefaultToolkit().getImage("images/yellow.GIF");
    boolean showYellow = true;
    int yellowX = 100;
    int yellowY = 100;
    
    
    public cmonDoIt(){

    }
    
    public Dimension getPreferredSize() {
      return new Dimension(600,600);
    }
    
    public void paintComponent(Graphics g) {
      super.paintComponent(g);

      g.drawImage(currentView, 0, 0, this);
      g.drawImage(yellow, yellowX, yellowY, this);

    }
    
    public void mousePressed(MouseEvent e) { }
    public void mouseClicked(MouseEvent e) { 

      int x = e.getX();
      int y = e.getY();

      wander();
      showYellow = true;

    }
    public void mouseEntered(MouseEvent e) { }
    public void mouseExited(MouseEvent e) {

    }
    public void mouseReleased(MouseEvent e) { }
    
    public void drawStuff(Graphics g, Image img, int x, int y){
      
      g.drawImage(img, x, y, this);
      
    }
 
    public void wander(){
      Random generator = new Random();
      int randomInt;
      for(int i = 0; i < 10; i++){
        randomInt = generator.nextInt(8);
        switch(randomInt){
          case 0:
            if(yellowY > 20){
            yellowY -= 40;
          }
            break;
          case 1:
            if(yellowY > 20){
            yellowY -= 40;
          }
            if(yellowX < 380){
              yellowX += 40;
            }
            break;
          case 2:
            if(yellowX < 380){
            yellowX += 40;
          }
            break;
          case 3:
            if(yellowY < 380){
            yellowY += 40;
          }
            if(yellowX < 380){
              yellowX += 40;
            }
            break;
          case 4:
            if(yellowY < 380){
            yellowY += 40;
          }
            break;
          case 5:
            if(yellowY < 380){
            yellowY += 40;
          }
            if(yellowX > 100){
              yellowX -= 40;
            }
            break;
          case 6:
            if(yellowX > 100){
              yellowX -= 40;
            }
            break;
          case 7:
            if(yellowY > 20){
            yellowY -= 40;
          }
            if(yellowX > 100){
              yellowX -= 40;
            }
            break;
          default:
        }
        System.out.println("X: " + yellowX + ", Y: " + yellowY);
        revalidate();
        repaint();   
        Wait.halfSec();
            
            
    }   
  }
}
}

i know its working because the println but no dice on the graphics update.

Recommended Answers

All 8 Replies

The problem is that all of that code is executing on (and holding up) the event queue, which is also responsible for repainting. You need to split that code off into a separate thread that calls for repaint() on the event queue when needed with EventQueue.invokeLater(Runnable) .

Here's a small example that may be of some help. I've commented the relevant sections

import java.awt.BorderLayout;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;

public class WorkThreadExample extends JFrame{

    JButton btnGo;
    JLabel output;

    public WorkThreadExample(){
        btnGo = new JButton("GO");
        btnGo.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                go();
            }
        });
        add(btnGo, BorderLayout.NORTH);

        output = new JLabel();
        add(output, BorderLayout.CENTER);

        setDefaultCloseOperation(EXIT_ON_CLOSE);
        setSize(200,200);
        setVisible(true);
    }

    /** called from button listener */
    private void go(){
        // start up a thread for our task and let the event queue get back to business
        Thread counter = new Thread(new CounterTask());
        counter.start();
        btnGo.setEnabled(false);
    }

    /** Small work task that we want to start from a UI event */
    class CounterTask implements Runnable{
        public void run() {
            try {
                for (int i = 0; i < 10; i++) {
                    final String countValue = String.valueOf(i);
                    // use this to put UI update tasks in the event queue
                    EventQueue.invokeLater(new Runnable() {
                        public void run() {
                            output.setText(countValue);
                        }
                    });
                    Thread.sleep(1000);
                }
                
                EventQueue.invokeLater(new Runnable() {
                    public void run() {
                        output.setText("Done");
                        btnGo.setEnabled(true);
                    }
                });
            } catch (InterruptedException ie) {
                Thread.currentThread().interrupt();
            }
        }
    }

    public static void main(String[] args) {
        EventQueue.invokeLater(new Runnable() {
            public void run() {
                WorkThreadExample ex = new WorkThreadExample();
            }
        });
    }
}

oh man oh man oh man it worked!!!
thanks you SO much Ezzaral, now i just need to adapt it for my real program

thanks a heap!

a quick question about this solution. is there anyway to make the program wait until the tread has completed before going on? i tried an empty
while(t.isAlive()) loop but it put me in the situation i was at before i converted to this thread approach. im having an issue because my game calls this for each piece under the computers control and id like them to go one at a time, but with this setup they all go at once.

thanks for you time

without reading all the code, though i looked through it, if you call repaint multiple times in different threads, or in one thread, assuring linearity, after each repaint(); you can do Thread.sleep(50); to sleep 50 milliseconds and sort of stop for now tell its repainted. Maybe have a counter in the draw function, shared by the repaint calling function, and do a quick sleep, and keep sleeping again and again tell the counter is incremented, meaning it painted.

If all your calls to repaint that can happen during that interval of time are in one thread, and take place in order with you not going past a sleep tell the counter is read, you dont have to worry about synrchonizing the counter variable, if repaint is called from different threads you have syronization issues. ideallly the thread that does this computer code and calls repaint, only is doing a read on this variable, so the writing only takes place in one thread. if not you have to synchronize as sleep can take place in the middle of executing a line of code, i.e. a=a+1 is actually several machine steps.

this also allows you to control the repainting over real time, i.e if you want the computer to execute repaint 9 times but take 5 seconds, a kind of slow motion animation, you can accomplish this.

Mike

another possiblity is to place your code within a timer.

you will need

import java.util.Timer;
import java.util.TimerTask;

the class should have a variable like

Timer timer;

the timer i'm using in my project now is called with:
timer.scheduleAtFixedRate( new ToDoTask ( ) , 1000 ,1000) ;

and you'll need a ToDoTask class like

class ToDoTask extends TimerTask  {
    public void run (  )   {

    z=z+10;
    repaint();

    System.out.println(" z is " + z + " in timer");
      //timer.cancel (  ) ; //Terminate the thread
    }
  }

this will go on scheduling forever every 1 second but you can schedule a timer to go off once and reschedule. different schedule methods you can use are at:

http://java.sun.com/j2se/1.5.0/docs/api/java/util/Timer.html

within the todo task you can use a variable to increment to tell which stage of the task you are on. place your code in the todo class. place that class within your class that is painting.
Mike

ahh thanks for your input but perhaps i was a bit unclear about my current situation.
basically what i am looking at now is that the original advice put part of my code into a separate thread, unfortunately this segment of code is called several times in succession (in a loop). this is creating multiple threads that are executing all at the same time when what i really want is for the events to happen sequentially.
i have tried a few ways to enforce the one at a time order that i desire. i tried a wait() notify() thing that i couldnt get to work right, probably due to my ignorance, it kept throwing exceptions at me, and i got a while(t.asAlive()){} empty busy loop to work, but when i did that it no longer repainted during the thread execution.

so

is there any way to have the threads execute one at a time and still have them repaint midway through their executions?

thanks for reading and sorry for any misunderstandings

The "turn" task can do whatever you need it to. Move one piece or one hundred. Just "disable" the player controls or whatever you do while the computer is taking its turn and when finished you return control to the player. The pieces under computer control can be kept in a list/queue/whatever and you just cycle through to move them. You don't really need separate threads for each move.

Be a part of the DaniWeb community

We're a friendly, industry-focused community of developers, IT pros, digital marketers, and technology enthusiasts meeting, networking, learning, and sharing knowledge.