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);
}
}