repaint with swing - paintComponent method never gets called...

Hi all,

New to this forum so this is my first post! I’m fairly new to Java game programming so I thought I would start myself off with a nice 2D game based around awt/swing components. I’m having problems calling the paintComponent method in one of my classes. I make a call to repaint() which makes a call to paint which then invokes paintComponent, if my understanding is correct. I therefore placed my paint code inside the overriden method paintComponent. However, when repaint is called, no call to paintComponent is made in the subclass, or parent class for that matter. I inserted break points into my code at the line where repaint is called and it the first line of my paintComponent method and then debugged the project in JBuilder which also confirmed the paintComponent method was not being called. Here is a copy of the class in question. As you can see it extends a class called GameWindow (which extends JPanel) which has its own paintComponent method.


class DealCards extends GameWindow{
    private static final long serialVersionUID = 1L;
    private static Logger log = Logger.getLogger(GameWindow.class.getName());
    private Rectangle startRect;
    private Rectangle compRect;
    private Rectangle playerRect;
    private int x;
    private int y;
    private final double SPEED = 19.0; // speed of cards
    private final long DELAY = 5; // delay between cards
    private final int MAX_CARDS = 9; //cards to deal in the sequence
    private int lastCard;
    private int currentCard;
    private int nextCard;
    private int startx = 200;
    private int starty = 175;
    private int comprx = 180;
    private int compry = 175;
    private int playerrx = 20;
    private int playerry = 367;
    private int count = 0;
    private boolean bPlayerMove = true;
    private boolean bCompMove = true;
    private boolean bDealing = false;
    private boolean bShufflingIsDone = false; // use later
    final private static String images_prefix = "C:/Documents and Settings/Paulo/jbproject/Canasta_Game/src/images/";

    BufferedImage nextcard1 = loadImage2(images_prefix + "card0.gif");
    BufferedImage currentcard1 = loadImage2(images_prefix + "card1.gif");
    BufferedImage lastcard1 = loadImage2(images_prefix + "card2.gif");

    public BufferedImage loadImage2(String name2){
        try {
            log.debug("ImageIO string is " + ImageIO.read(new File(name2)));
            BufferedImage im = ImageIO.read(new File(name2));
            GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
            GraphicsDevice gs = ge.getDefaultScreenDevice();
            GraphicsConfiguration gc = gs.getDefaultConfiguration();
            int transparency = im.getColorModel().getTransparency();
            BufferedImage copy = gc.createCompatibleImage(im.getWidth(), im.getHeight(), transparency );
            Graphics2D g2d = copy.createGraphics();
            g2d.drawImage(im, 0, 0, null);
            g2d.dispose();
            return copy;
        } catch (IOException ex) {
            log.error("Load image error: " + ex);
            new ErrorWindow(ex.toString(), getClass().toString());
            return null;
        }
    }

    public void shuffleWait2() {
        log.debug("SHUFFLEWAIT2 CALLED");
        Thread shuffleThread = new Thread(shuffler2);
        shuffleThread.setPriority(Thread.NORM_PRIORITY);
        shuffleThread.start();
    }

    private void initRects() {
        int cardw = 70;
        int cardh = 100;
        startRect = new Rectangle(startx, starty, cardw, cardh);
        compRect = new Rectangle(comprx, compry, cardw, cardh);
        playerRect = new Rectangle(playerrx, playerry, cardw, cardh);
        this.x = startRect.x;
        this.y = startRect.y;
    }

    public void dealCard2() {
        start();
    }

    public void dealCards2() {
        log.debug("DEALCARDS2 CALLED");
        if(!bDealing) {
            new SoundApplet2("Deal.wav");
            Thread dealThread = new Thread(dealer);
            dealThread.setPriority(Thread.NORM_PRIORITY);
            dealThread.start();
        }
    }

    public void run() {
        while(bDealing) {
            try {
                Thread.sleep(DELAY);
            } catch (InterruptedException ex) {
                log.error("Dealing thread interrupted");
                new ErrorWindow(ex.toString(), getClass().toString());
                stop();
            }
            boolean finished = advanceCard2();
            if(finished) {
                stop();
            }
        }
    }

    public void start() {
        if(!bDealing) {
            next2();
            count = 0;
            bDealing = true;
            thread = new Thread(this);
            thread.setPriority(Thread.NORM_PRIORITY);
            thread.start();
        }
    }

    private void stop() {
        bDealing = false;
        if(nextCard == MAX_CARDS) {
            bShufflingIsDone = true;
        }
        if(thread != null)
            thread.interrupt();
        thread = null;
    }

    private boolean advanceCard2() {
        Point p1 = startRect.getLocation();
        Point p2 = playerRect.getLocation();
        Point p3 = compRect.getLocation();
        if(bPlayerMove) {
            double distance = p1.distance(p2);
            double d = ++count * SPEED;
            double x = p1.x + d;
            if (d > distance)
                x -= d - distance;
            moveCard2(x, p1.y);
            bCompMove = true;
            bPlayerMove = false;
            return d > distance;
        }
        if(bCompMove) {
            double distance = p1.distance(p3);
            double d = ++count * SPEED;
            double x = p1.x + d;
            if (d > distance)
                x -= d - distance;
            moveCard2(x, p1.y);
            bPlayerMove = true;
            bCompMove = false;
            return d > distance;
        }
        return true;
    }

    public void moveCard2(double x, double y) {
        this.x = (int)x;
        this.y = (int)y;
        //repaint(145,175,220,100);
        repaint();
    }

    public void next2() {
        if(nextCard+1 > 9)
            resetCards2();
        lastCard = currentCard;
        currentCard = nextCard;
        nextCard++;
        if(startRect == null)
            initRects();
        GameWindow.card_count--; // as we deal a card we take one off the stack
        x = startRect.x;
        y = startRect.y;
        log.debug("card_count is " + GameWindow.card_count);
    }

    public void resetCards2() {
        lastCard = -2;
        currentCard = -1;
        nextCard = 0;
        //repaint(145,175,220,100);
        repaint();
    }

    Runnable shuffler2 = new Runnable() {
        public void run() {
            log.debug("SHUFFLER2 RUNNER CALLED");
            int count = 0;
            for(; count < 1;){
                try {
                    log.debug("Sleep for 500ms");
                    Thread.sleep(500);
                    dealCards2();
                    count++;
                } catch (InterruptedException ex) {
                    log.error("runner thread error " + ex);
                    new ErrorWindow(ex.toString(), getClass().toString());
                }
            }
        }
    };

    Runnable dealer = new Runnable() {
        public void run() {
            int count = 0;
            final int WAIT_FOR_END_OF_DEAL = 50;
            while(count++ < MAX_CARDS) {
                dealCard2();
                while(bDealing) {
                    try {
                        Thread.sleep(WAIT_FOR_END_OF_DEAL);
                    } catch(InterruptedException ex) {
                        log.error("runner thread error " + ex);
                        new ErrorWindow(ex.toString(), getClass().toString());
                        stop();
                    }
                }
            }
        }
    };

    protected void paintComponent(Graphics g){
        if (bDealing) {
            try {
                Graphics2D g2 = (Graphics2D) g;
                log.debug("draw a card");
                g2.drawImage(nextcard1, startRect.x, startRect.y, this);
                if (lastCard > -1) {
                    g2.drawImage(lastcard1, playerRect.x, playerRect.y, this);
                    g2.drawImage(lastcard1, compRect.x, compRect.y, this);
                }
                if (currentCard > -1)
                    g2.drawImage(currentcard1, x, y, this);
            } catch (Exception ex) {
                log.error("Exception " + ex.toString());
                new ErrorWindow(ex.toString(), getClass().toString());
            }
        }
    }
}

I’m guessing this is probably a fairly straight forward question. I read the following article http://java.sun.com/products/jfc/tsc/articles/painting/index.html and I seem to be following everything that they say on here so I’m not sure what I’m doing wrong. The only way I can get it to work is if I put all this code into the GameWindow class but it’s getting quite big and so is the paintComponent method in it so I would like to break the classes down into parts that all perform seperate actions of the game to make it easier to understand.

Thanks.

your calling hierarchy guess is correct… repaint() calls paintComponent() but only not instantly, as fast as it can…
maybe your bDealing is false always? Put some output before it to check this and if paintComponent() really gets called, you don’t need debugger…
one more thing that could make paintComponent() not get called is that you never added it to a visible container… also check if repaint() really gets called by putting some output before it.

ok i modified the moveCard2 and paintComponenet methods to the following so some more output would be displayed:


    public void moveCard2(double x, double y) {
        this.x = (int)x;
        this.y = (int)y;
        //repaint(145,175,220,100);
        log.debug("about to call repaint from moveCards2");
        log.debug("value of bDealing is " + bDealing);
        repaint();
        log.debug("repaint called from moveCards2");
    }

    protected void paintComponent(Graphics g){
        log.debug("inside DealCards paintComponent method");
        log.debug("value of bDealing is " + bDealing);
        if (bDealing) {
            try {
                Graphics2D g2 = (Graphics2D) g;
                log.debug("draw a card");
                g2.drawImage(nextcard1, startRect.x, startRect.y, this);
                if (lastCard > -1) {
                    g2.drawImage(lastcard1, playerRect.x, playerRect.y, this);
                    g2.drawImage(lastcard1, compRect.x, compRect.y, this);
                }
                if (currentCard > -1)
                    g2.drawImage(currentcard1, x, y, this);
            } catch (Exception ex) {
                log.error("Exception " + ex.toString());
                new ErrorWindow(ex.toString(), getClass().toString());
            }
        }
    }

so now in my log i just see the following:

2007-08-20 01:13:15,406 [GameWindow.java] DEBUG - about to call repaint from moveCards2 (Thread-27)
2007-08-20 01:13:15,406 [GameWindow.java] DEBUG - value of bDealing is true (Thread-27)
2007-08-20 01:13:15,406 [GameWindow.java] DEBUG - repaint called from moveCards2 (Thread-27)

and nothing more. As for your point about a visivle container, well this could be where I have made the mistake. My assumption (probably wrong) is that GameWindow extends JPanel and therefore by DealCards extending GameWindow it would therefore be extending JPanel too since the paintComponent method works fine in the GameWindow class. ???

hello!
I think you missed the MediaTracker to get synched with the loading of your bufferedImage. Plus I’d put the log for “repaint called”, quoting your code, in the paintComponent() method which will better reflect to time when the painting is actually made.
That is the point, each time you want to draw a new Image, add it to the MediaTracker and wait for it to load completely before to start rendering ! :smiley:
MediaTracker javadoc

The MediaTracker is not essential, all it does is ensure the image is loaded before it attempts to draw it and provides you with a way of prioritising the order in which images are loaded.

My problem is that the paintComponent method is not even being called. I have a line in the loadImage method which displays a line of output to my log file when an image is loaded, also in my loadImage method you can see the image is drawn and then dispose is called so this is not the problem.

My GameWindow class uses the same methodology, eg, same loadImage function with paintComponent method and this works fine.

Regardless of where I put my output lines, the fact is when I debug in JBuilder and step into every function called the paintComponent method is never called and as you can see from the output, the value of bDealer is true.

What top-level Container are you placing the JPanel into?

If it isn’t a JFrame, JDialog, or JApplet (or JWindow), then that is the cause of your problem.

http://java.sun.com/docs/books/tutorial/uiswing/components/toplevel.html

a JFrame initiated in my Main class

hmm… are you sure you are overriding it? Maybe method from super class has public modifier and not protected? Didn’t ever tried that so I don’t know if it matters.
Well you said it already works for GameWindow so this doesn’t matter…

Also, you cannot go into paintComponent() while debugging because paintComponent() is called from AWT-Swing thread (EDT?), it isn’t called from repaint() in your thread. Repaint() only queues call to paintComponent() in Swing thread.

I don’t know how we can help you anymore… make sure that it’s visible (setVisible(true)) and that is added to a visible container.

If you don’t solve it, instead if this build simplest JPanel with custom painting and try to implement it. If it works then build it little by little to your class.

Does your super-class GameWindow override paint(Graphics) or update(Graphics) ?

In reply to Kova, the paintComponenent method in my super class is set to protected too. The JFrame state is visible and other calls to paintComponent from GameWindow are executed and drawn in the JPane.

In reply to Abuse, GameWindow only overrides paintComponent

I’d be surprised that your GameWindow doesn’t paint at all if you were actually calling the repaint() in one main Thread, but I can distiguish that the start() method does invoke the Runnable that is defined with the run(). Then you must have the call to repaint() written in one of those 2 methods, which is obviously not the case. Would you check for that? ???

how about size? Did you set the size?
edit: clarification, maybe you are using null layout manager and in that case you need to set size and position manually as I remember or they will be zero and paintComponent() wouldn’t get called (?) … try to do it manually

my main JFrame has the setSize attribute set, no setLocation but it renders fine… as for the call to repaint in the start/run method, I tried placing it in there and still no joy.

I’m feeling rather like that animated gif you see around the internet every now and then of the stick man bashing his head against the keyboard! :smiley:

I wasn’t talking about JFrame, I was talking about your container and DealCards. Everything must be visible, put setVisible(true) just in case for every component. By default if you make JFrame visible then all children will be visible, but if you used some unstandard layout manager as null layout manager then you have to set sizes manually, I’m not talking about JFrame but for container.

edit: here’s a simple example, if you uncomment any setVisible(false) or setLayout(null) the button will not appear

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;

public class ComponentVisibility extends JFrame {
    
    JPanel container = new JPanel();
    JButton button = new JButton("test");
    
    public ComponentVisibility() {
        super("Component Visibility");
        setDefaultCloseOperation(EXIT_ON_CLOSE);
        setBounds(200,200,300,400);
        setContentPane(container);
//        container.setLayout(null);
        container.add(button);
//        container.setVisible(false);
//        button.setVisible(false);
        setVisible(true);
    }
    
    public static void main(String[] args) {
        new ComponentVisibility();
    }

}

In the end I could only get it to work when I merged the DealCards class code with the GameWindow class code… oh well. Now to have fun getting the cards to be dealt to the correct places on the table! ;D