Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
133 views
in Technique[技术] by (71.8m points)

java - Rendering with a timer and paintComponent is causing no rendering to happen

I've been trying to develop a game, and I'm in the preliminary stages of getting all the seperate components to work (hence my terrible code) and while trying to render a moving square, I've run into an issue. The paintComponent method will draw squares, but I cant get it to work when I'm using a timer.

I've tried moving the timer method around, using repaint at different points, and at one point scrapped everything and redid it from the ground up.

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Toolkit;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.time.Clock;
import java.time.Duration;
import java.util.Timer;
import java.util.TimerTask;

import javax.swing.JFrame;

public class InputTest extends MyPanel implements KeyListener {
    private static int x;
    private static int y;

    public static void main(String[] args) {
        JFrame f = new JFrame();
        InputTest p = new InputTest();
        f.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
        f.setSize(500, 500);
        p.setSize(500, 500);
        f.add(p);
        f.addKeyListener(new InputTest());
        Timer timer = new Timer();
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                x++;
                y++;
                if (x > 500) {
                    x = 0;
                }
                if (y > 500) {
                    y = 0;
                }
                System.out.println("Tick"+x+","+y);

            }
        }, 0, 500);

        p.setVisible(true);
        f.setVisible(true);
    }

    @Override

    public void keyPressed(KeyEvent e) {

    }

    @Override
    public void keyReleased(KeyEvent e) {
        // TODO Auto-generated method stub

    }

    @Override
    public void keyTyped(KeyEvent e) {
        // TODO Auto-generated method stub

    }

    public void update() {

    }

    @Override
    protected void paintComponent(Graphics g) {
        g.setColor(Color.BLACK);
        while(x!=501&&y!=501) {
            g.fillRect(x, y, 30, 30);
            repaint();
        }
    }
}

The square should be moving slowly from the top left to the bottom right. Instead, I'm not getting any image.

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

Swing is single threaded

When Swing triggers a paint cycle, the results will not be rendered to the screen until all components in the hierarchy have completed their operations.

This means that when you do something like...

@Override
protected void paintComponent(Graphics g) {
    g.setColor(Color.BLACK);
    while (x != 501 && y != 501) {
        g.fillRect(x, y, 30, 30);
        repaint();
    }
}

Nothing will happen until you exit the paintComponent method, meaning, in this case, the last thing you painted is what will be rendered (or in this a nice streak).

As a side note, you should NEVER call repaint directly or indirectly from within a paint method, this is a really good way to consume all the CPU cycles and bring your system to its knees.

Instead, painting should simply paint the current/desired state. Also, unless you really know what you're doing, you should always call super.paintComponent first, for example...

@Override
protected void paintComponent(Graphics g) {
    super.paintComponent(g);
    g.setColor(Color.BLACK);
    g.fillRect(x, y, 30, 30);
}

See Concurrency in Swing for more details

Swing is not thread safe

You should always avoid updating the UI from out side the Event Dispatching Thread. This also includes updating states which the UI relies on. Doing so can cause all sorts of difficult to diagnose glitches.

Since java.util.Timer uses its own Thread to perform its scheduling, it is a poor candidate for using in Swing. Instead, you should be using javax.swing.Timer, which will wait outside the EDT, but trigger it's assigned ActionListener which the context of the EDT

For example...

Timer timer = new Timer(500, new ActionListener() {
    @Override
    public void actionPerformed(ActionEvent e) {
        x++;
        y++;
        if (x > 500) {
            x = 0;
        }
        if (y > 500) {
            y = 0;
        }

        // Trigger a new paint cycle...
        repaint();
    }
});
timer.start();

See How to Use Swing Timers for more details

All the "other" stuff

KeyListener is best avoided, for a number of reasons. Use the Key Bindings API, it will save you a lot of hair pulling.

The available size of your component is always the size of the window MINUS its border decorations. This means that using f.setSize(500, 500) will make the available context area smaller (depending on the platform you are running your code).

Better to overriding the component's getPreferredSize method and return a sizing hint. You can then use JFrame#pack to "pack" the window around the content, so that the window is larger then the content size, but you won't waste time trying to figure out why things don't stop where you expect them to.

Runnable example

I also modified the code to remove the reliance on static, which is never a great idea

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;

public class InputTest extends JPanel {

    private int x;
    private int y;

    public static void main(String[] args) {
        JFrame f = new JFrame();
        InputTest p = new InputTest();
        f.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
        f.add(p);
        f.pack();
        f.setLocationRelativeTo(null);
        f.setVisible(true);
    }

    public InputTest() {
        Timer timer = new Timer(500, new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                x++;
                y++;
                if (x > 500) {
                    x = 0;
                }
                if (y > 500) {
                    y = 0;
                }

                // Trigger a new paint cycle...
                repaint();
            }
        });
        timer.start();
    }

    @Override
    public Dimension getPreferredSize() {
        return new Dimension(500, 500);
    }

    @Override
    protected void paintComponent(Graphics g) {
        g.setColor(Color.BLACK);
        g.fillRect(x, y, 30, 30);
    }
}

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...