Alright, so I have two JFrames each with a different implementation of JPanel sitting inside of them. When I call repaint() on the JPanels, what is painted on one JPanel also becomes painted on the other JPanel.
I know I could take care of this by calling something like g.clearRect(), but there are too many components to repaint every time.
Any idea why this is happening?
//This makes the two JFrames and JPanels, sets everything up
public void makeSpaceSimulation() {
int dimen = 10000;
int scale = 20;
int numOfClusters = 30;
int planPerCluster = 2000;
SpaceShip s = new SpaceShip(dimen, scale);
QuadTree t = new QuadTree(dimen, 20);
new PlanetCreationTest(t, dimen, scale, numOfClusters, planPerCluster);
makeMap(dimen, scale, s, t);
makePOV(s, t, scale, dimen);
}
public void makeMap(int dimen, int scale, SpaceShip s, QuadTree t) {
final JFrame f = new JFrame();
f.setSize(dimen / scale, dimen / scale);
f.setLocation(0, 0);
f.setTitle("Map Panel");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
mapP = new MapPanel(scale, s, dimen);
mapP.setLayout(new BorderLayout());
mapP.addTree(t);
f.add(mapP);
f.setVisible(true);
Insets i = f.getInsets();
f.setSize(dimen / scale + (i.left + i.right) + 2, dimen / scale
+ (i.top + i.bottom) + 2);
Thread th = new Thread() {
public void run() {
while (true) {
mapP.repaint();
}
}
};
th.start();
}
public void makePOV(final SpaceShip s, QuadTree t, int scale, int dimen) {
Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
JFrame f = new JFrame();
f.setSize(500, 500);
f.setLocation(screenSize.width - 500, 0);
f.setTitle("POV Panel");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
povP = new POVPanel(s, scale, dimen);
povP.setLayout(new BorderLayout());
povP.addTree(t);
povP.setFocusable(true);
povP.addKeyListener(new KeyListener() {
#Override
public void keyPressed(KeyEvent arg0) {
final int i = arg0.getKeyCode();
Thread th = new Thread() {
public void run() {
if (i == 39) {
s.moveRight();
} else if (i == 37) {
s.moveLeft();
} else if (i == 40) {
s.moveDown();
} else if (i == 38) {
s.moveUp();
}
try {
Thread.sleep(500);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
};
th.start();
}
#Override
public void keyReleased(KeyEvent arg0) {
}
#Override
public void keyTyped(KeyEvent arg0) {
}
});
f.add(povP);
f.setVisible(true);
Insets i = f.getInsets();
f.setSize(dimen / 20 + (i.left + i.right) + 2, dimen / 20
+ (i.top + i.bottom) + 2);
Thread th = new Thread() {
public void run() {
while (true) {
povP.repaint();
}
}
};
th.start();
}
//here's the MapPanel
public class MapPanel extends JPanel {
private QuadTree q;
private int scale;
private int dimen;
private SpaceShip s;
private boolean firstDraw;
public MapPanel(int scale, SpaceShip s, int dimen) {
this.dimen = dimen;
q = new QuadTree(0, 0);
this.scale = scale;
this.s = s;
firstDraw = true;
}
public void addTree(QuadTree q) {
this.q = q;
}
public void paintComponent(Graphics g) {
if (firstDraw) {
q.draw(g, scale, new Point(0, 0));
s.drawScaledGeometry(g);
System.out.println("Totally drew that");
firstDraw = false;
} else {
g.clearRect(s.viewDistance.x/scale, s.viewDistance.y/scale,
s.viewDistance.width/scale, s.viewDistance.height/scale);
q.quadDraw(g, scale, s.viewDistance, new Point(0, 0));
s.drawScaledGeometry(g);
}
}
}
//and this is the POVPanel
public POVPanel(SpaceShip s, int scale, int dimen) {
super();
this.s = s;
// this.scale = scale;
this.dimen = dimen;
}
public void addTree(QuadTree q) {
this.q = q;
}
public void paintComponent(Graphics g) {
g.clearRect(0, 0, dimen / 20, dimen / 20);
q.quadDraw(g, 1, s.viewDistance, s.getMoved());
s.drawGeometry(g);
}
This is (one) of your problems...
public void paintComponent(Graphics g) {
if (firstDraw) {
q.draw(g, scale, new Point(0, 0));
s.drawScaledGeometry(g);
System.out.println("Totally drew that");
firstDraw = false;
} else {
g.clearRect(s.viewDistance.x/scale, s.viewDistance.y/scale,
s.viewDistance.width/scale, s.viewDistance.height/scale);
q.quadDraw(g, scale, s.viewDistance, new Point(0, 0));
s.drawScaledGeometry(g);
}
}
The Graphics context is shared during the paint cycle, meaning when you get it, you will be getting the same context used to paint all the other components on the screen.
You MUST call super.paintComponent(g), which will take care of preparing the graphics context for this component to start painting.
Update #1
Again...
public void paintComponent(Graphics g) {
super.paintComponent(g); // <-- Call me instead...
//g.clearRect(0, 0, dimen / 20, dimen / 20);
q.quadDraw(g, 1, s.viewDistance, s.getMoved());
s.drawGeometry(g);
}
Update #2
I'm also seriously concerned by this
while (true) {
mapP.repaint();
}
This could seriously impact the performance of you application. You DO NOT control the paint cycle, that's the responsibility of the repaint manager. The repaint manager will decide when a repaint is required. Calling repaint repetitively like this could actually cause the repaint manager to delay actually scheduling a paint cycle.
Much better to use a javax.swing.Timer, it's simpler and safer...
Timer timer = new Timer(25, new ActionListener() {
public void actionPerformed(ActionEvent evt) {
mapP.repaint();
}
});
timer.setRepeats(true);
timer.setCoalesce(true);
timer.start();
You might find reading through
Performing Custom Painting
Painting in AWT and Swing
Helpful
Update #3
Avoid KeyListener, it will only make you cry. Instead, use the key bindings API
Related
I am learning Java currently and have been given the assignmnet of finidhing off a program to create the game Conways's life (we started with some code provided to us and must add features etc to this).
I am currently stuck on a menu option for the game. I want it to start off at the menu screen, wherein buttons appear at the top for "Start", "Random", "Load", Save". I have written code so that the program displays these buttons, through a fillRect option in my paint method.
My question is, how do I use the mousePressed method to recognise the cells selected so that I can get an action to occur when they are selected. I been looking at this for a while but can't seem to get this working.
Any suggestion would be a massive help. I have shared my code below. It's a work in progress but I would really like to get this working before continuing on with the other functionality.
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import java.awt.image.*;
public class ConwaysLife extends JFrame implements Runnable, MouseListener {
// member data
private BufferStrategy strategy;
private Graphics offscreenBuffer;
private boolean gameState[][] = new boolean[40][40];
private boolean isGameInProgress = false;
// constructor
public ConwaysLife () {
//Display the window, centred on the screen
Dimension screensize = java.awt.Toolkit.getDefaultToolkit().getScreenSize();
int x = screensize.width/2 - 400;
int y = screensize.height/2 - 400;
setBounds(x, y, 800, 800);
setVisible(true);
this.setTitle("Conway's game of life");
// initialise double-buffering
createBufferStrategy(2);
strategy = getBufferStrategy();
offscreenBuffer = strategy.getDrawGraphics();
// register the Jframe itself to receive mouse events
addMouseListener(this);
// initialise the game state
for (x=0;x<40;x++) {
for (y=0;y<40;y++) {
gameState[x][y]=false;
}
}
// create and start our animation thread
Thread t = new Thread(this);
t.start();
}
// thread's entry point
public void run() {
while ( 1==1 ) {
// 1: sleep for 1/5 sec
try {
Thread.sleep(200);
} catch (InterruptedException e) { }
// 2: animate game objects [nothing yet!]
/*if (isGameInProgress == false) {
this.repaint();
}*/
// 3: force an application repaint
this.repaint();
}
}
// mouse events which must be implemented for MouseListener
public void mousePressed(MouseEvent e) {
while (!isGameInProgress) {
int x = e.getX()/20;
int y = e.getY()/20;
if(x >= 10 && x <= 80 && y >= 40 && y <= 65) {
isGameInProgress = !isGameInProgress;
this.repaint();
}
}
// determine which cell of the gameState array was clicked on
int x = e.getX()/20;
int y = e.getY()/20;
// toggle the state of the cell
gameState[x][y] = !gameState[x][y];
// request an extra repaint, to get immediate visual feedback
this.repaint();
}
public void mouseReleased(MouseEvent e) { }
public void mouseEntered(MouseEvent e) { }
public void mouseExited(MouseEvent e) { }
public void mouseClicked(MouseEvent e) { }
//
// application's paint method
public void paint(Graphics g) {
Font font = new Font("Veranda", Font.BOLD, 20);
g = offscreenBuffer; // draw to off screen buffer
// clear the canvas with a big black rectangle
g.setColor(Color.BLACK);
g.fillRect(0, 0, 800, 800);
/*look to add a while game in progress loop here!!!*/
// draw menu options
if(!isGameInProgress) {
g.setColor(Color.green);
g.fillRect(10, 40, 70, 25);
g.fillRect(100, 40, 100, 25);
g.fillRect(300, 40, 170, 25);
g.setColor(Color.BLACK);
g.setFont(font);
g.drawString("Start", 15, 60);
g.drawString("Random", 105, 60);
g.drawString("Load", 305, 60);
g.drawString("Save", 395, 60);
}
// redraw all game objects
g.setColor(Color.WHITE);
for (int x=0;x<40;x++) {
for (int y=0;y<40;y++) {
if (gameState[x][y]) {
g.fillRect(x*20, y*20, 20, 20);
}
}
}
// flip the buffers
strategy.show();
}
// application entry point
public static void main(String[] args) {
ConwaysLife w = new ConwaysLife();
}
}
You're not going to like the answer, but it's the "correct" way to approach the problem.
What you need to understand is, Swing/AWT is using a "passive" rendering workflow and BufferStrategy is using a "active" rendering workflow, these are incompatible with each other.
As a general rule, you should not be overriding paint of top level containers like JFrame, this is going to end in no end of issues. Instead, you should be starting with something like JPanel and overriding it's paintComponent method instead.
Having said that, there's a "simpler" solution available to you. CardLayout. This will allow you to seperate the workflows of the menu from the game and resolve the issue between Swing/AWT and BufferStrategy
For example...
import java.awt.Canvas;
import java.awt.CardLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferStrategy;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
public final class Main {
public static void main(String[] args) {
new Main();
}
public Main() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
JFrame frame = new JFrame();
frame.add(new MainPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class MainPane extends JPanel {
enum View {
MENU, GAME;
}
private CardLayout cardLayout = new CardLayout();
private GamePane gamePane;
public MainPane() {
setLayout(cardLayout);
gamePane = new GamePane();
add(new MenuPane(new MenuPane.Observer() {
#Override
public void startNewGame() {
showGame();
}
#Override
public void randomGame() {
}
#Override
public void loadGame() {
}
#Override
public void saveGame() {
}
}), View.MENU);
add(gamePane, View.GAME);
}
protected void add(Component compent, View view) {
add(compent, view.name());
}
protected void showGame() {
show(View.GAME);
gamePane.start();
}
protected void showMenu() {
gamePane.stop();
show(View.MENU);
}
protected void show(View view) {
cardLayout.show(this, view.name());
}
}
public class MenuPane extends JPanel {
public interface Observer {
public void startNewGame();
public void randomGame();
public void loadGame();
public void saveGame();
}
private Observer observer;
public MenuPane(Observer observer) {
this.observer = observer;
JButton startButton = new JButton("Start");
JButton randomButton = new JButton("Random");
JButton loadButton = new JButton("Load");
JButton saveButton = new JButton("Save");
setLayout(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
gbc.ipadx = 10;
gbc.ipady = 10;
gbc.insets = new Insets(8, 8, 8, 8);
gbc.weightx = GridBagConstraints.REMAINDER;
add(startButton, gbc);
add(randomButton, gbc);
add(loadButton, gbc);
add(saveButton, gbc);
startButton.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
observer.startNewGame();
}
});
randomButton.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
observer.randomGame();
}
});
loadButton.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
observer.loadGame();
}
});
saveButton.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
observer.saveGame();
}
});
}
}
public class GamePane extends Canvas {
private Thread thread;
private volatile boolean isRunning = false;
public GamePane() {
setBackground(Color.BLACK);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(800, 800);
}
protected void start() {
if (isRunning) {
return;
}
createBufferStrategy(3);
isRunning = true;
thread = new Thread(new Runnable() {
#Override
public void run() {
mainLoop();
}
});
thread.start();
}
protected void stop() {
if (!isRunning || thread == null) {
return;
}
isRunning = false;
try {
thread.join();
} catch (InterruptedException ex) {
}
thread = null;
}
protected void mainLoop() {
try {
while (isRunning) {
render();
Thread.sleep(16);
}
} catch (InterruptedException ex) {
}
}
protected void render() {
BufferStrategy strategy = getBufferStrategy();
if (strategy == null) {
return;
}
// Render single frame
do {
// The following loop ensures that the contents of the drawing buffer
// are consistent in case the underlying surface was recreated
do {
// Get a new graphics context every time through the loop
// to make sure the strategy is validated
Graphics graphics = strategy.getDrawGraphics();
FontMetrics fm = graphics.getFontMetrics();
String text = "All your game are belong to us";
int x = (getWidth() - fm.stringWidth(text)) / 2;
int y = (getHeight() - fm.getHeight()) / 2;
graphics.setColor(Color.WHITE);
graphics.drawString(text, x, y + fm.getAscent());
// Render to graphics
// ...
// Dispose the graphics
graphics.dispose();
// Repeat the rendering if the drawing buffer contents
// were restored
} while (strategy.contentsRestored());
// Display the buffer
strategy.show();
// Repeat the rendering if the drawing buffer was lost
} while (strategy.contentsLost());
}
}
}
I would strongly recommend that you take the time to read through:
Creating a GUI With Swing
A Visual Guide to Layout Managers
Performing Custom Painting
Painting in AWT and Swing
BufferStrategy and BufferCapabilities
JavaDocs for BufferStrategy which demonstrate how the API should be used.
A "fully" BufferStrategy based approach...
Now, if you can't use Swing, "for reasons", you can still achieve a simular concept using "delegation".
Basically this means "delegating" responsibility for performing some workflow to another. In this case, we want to delegate the rendering and the handling of the mouse events.
This allows you to have a dedicated workflow for the menu and a dedicated workflow for the game, without having to try and mix a lot of state.
Why do I keep on insisting on separating these two workflows? Simply, because it makes it MUCH easier to manage and reason about, but also because it supports the Single Responsibility Principle.
The follow example makes use of Renderable interface to define the core functionality that end "render" delegate will need to implement, in this case, it's pretty simple, we want to tell the renderer to "render" it's content on each paint pass and we want to (optionally) delegate mouse clicked events (this could be done via a second interface or even just the MouseListener interface directly, but I've made it a requirement of the Renderable interface for demonstration purposes.
The "basic" solution to your actual question is found through the use of Rectangle#contains(Point).
This basically inspects each "button" Rectangle to determine if the supplied MouseEvent occurs within it's bounds, if it does, we take action.
It is, however, a little more complicated then that, as we need to have the Rectangles built ahead of time, not difficult, but it's state which is actually reliant on the parent, as we need to know the area in which the renderer is been displayed, run the example, you'll see what I mean 😉
import java.awt.Canvas;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.FontMetrics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.geom.RoundRectangle2D;
import java.awt.image.BufferStrategy;
import javax.swing.JFrame;
public final class Main {
public static void main(String[] args) {
new Main();
}
public Main() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
MainPane mainPane = new MainPane();
JFrame frame = new JFrame();
frame.add(mainPane);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
mainPane.start();
}
});
}
public interface Renderable {
public void render(Graphics2D g2d, Dimension size);
// We could just extend from MouseListener
// but I don't need all those event handlers
public void mouseClicked(MouseEvent e);
}
public class MainPane extends Canvas {
private Thread thread;
private volatile boolean isRunning = false;
private Renderable currentRenderer;
private MenuRenderer menuRenderer;
private GameRenderer gameRenderer;
public MainPane() {
setBackground(Color.BLACK);
gameRenderer = new GameRenderer();
menuRenderer = new MenuRenderer(new MenuRenderer.Observer() {
#Override
public void startNewGame() {
showGame();
}
#Override
public void randomGame() {
}
#Override
public void loadGame() {
}
#Override
public void saveGame() {
}
});
showMenu();
addMouseListener(new MouseAdapter() {
#Override
public void mouseClicked(MouseEvent e) {
if (currentRenderer == null) {
return;
}
currentRenderer.mouseClicked(e);
}
});
}
protected void showMenu() {
// This may need to tell the game renderer to stop
// or pause
currentRenderer = menuRenderer;
}
protected void showGame() {
currentRenderer = gameRenderer;
}
#Override
public Dimension getPreferredSize() {
return new Dimension(800, 800);
}
protected void start() {
if (isRunning) {
return;
}
createBufferStrategy(3);
isRunning = true;
thread = new Thread(new Runnable() {
#Override
public void run() {
mainLoop();
}
});
thread.start();
}
protected void stop() {
if (!isRunning || thread == null) {
return;
}
isRunning = false;
try {
thread.join();
} catch (InterruptedException ex) {
}
thread = null;
}
protected void mainLoop() {
try {
while (isRunning) {
render();
Thread.sleep(16);
}
} catch (InterruptedException ex) {
}
}
protected void render() {
BufferStrategy strategy = getBufferStrategy();
if (strategy == null && currentRenderer != null) {
return;
}
// Render single frame
do {
// The following loop ensures that the contents of the drawing buffer
// are consistent in case the underlying surface was recreated
do {
// Get a new graphics context every time through the loop
// to make sure the strategy is validated
Graphics2D g2d = (Graphics2D) strategy.getDrawGraphics();
g2d.setBackground(Color.BLACK);
g2d.fillRect(0, 0, getWidth(), getHeight());
RenderingHints hints = new RenderingHints(
RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON
);
g2d.setRenderingHints(hints);
// Render to graphics
currentRenderer.render(g2d, getSize());
// Dispose the graphics
g2d.dispose();
// Repeat the rendering if the drawing buffer contents
// were restored
} while (strategy.contentsRestored());
// Display the buffer
strategy.show();
// Repeat the rendering if the drawing buffer was lost
} while (strategy.contentsLost());
}
}
public class GameRenderer implements Renderable {
#Override
public void render(Graphics2D g2d, Dimension size) {
FontMetrics fm = g2d.getFontMetrics();
String text = "All your game are belong to us";
int x = (size.width - fm.stringWidth(text)) / 2;
int y = (size.height - fm.getHeight()) / 2;
g2d.setColor(Color.WHITE);
g2d.drawString(text, x, y + fm.getAscent());
}
#Override
public void mouseClicked(MouseEvent e) {
}
}
public class MenuRenderer implements Renderable {
public interface Observer {
public void startNewGame();
public void randomGame();
public void loadGame();
public void saveGame();
}
private Observer observer;
private String[] menuOptions = new String[]{
"New Game",
"Random",
"Load Game",
"Save Game"
};
private Rectangle[] menuBounds;
private int internalPadding = 20;
private int horizontalGap = 16;
public MenuRenderer(Observer observer) {
this.observer = observer;
}
#Override
public void render(Graphics2D g2d, Dimension size) {
if (menuBounds == null) {
createMenus(g2d, size);
}
renderMenus(g2d);
}
protected void createMenus(Graphics2D g2d, Dimension size) {
FontMetrics fm = g2d.getFontMetrics();
int totalHeight = (((fm.getHeight() + internalPadding) + horizontalGap) * menuOptions.length) - horizontalGap;
int buttonHeight = fm.getHeight() + internalPadding;
menuBounds = new Rectangle[menuOptions.length];
int buttonWidth = 0;
for (int index = 0; index < menuOptions.length; index++) {
int width = fm.stringWidth(menuOptions[index]) + internalPadding;
buttonWidth = Math.max(width, buttonWidth);
}
int yPos = (size.height - totalHeight) / 2;
for (int index = 0; index < menuOptions.length; index++) {
int xPos = (size.width - buttonWidth) / 2;
Rectangle menuRectangle = new Rectangle(xPos, yPos, buttonWidth, buttonHeight);
menuBounds[index] = menuRectangle;
yPos += buttonHeight + (horizontalGap / 2);
}
}
protected void renderMenus(Graphics2D g2d) {
for (int index = 0; index < menuOptions.length; index++) {
String text = menuOptions[index];
Rectangle bounds = menuBounds[index];
renderMenu(g2d, text, bounds);
}
}
protected void renderMenu(Graphics2D g2d, String text, Rectangle bounds) {
FontMetrics fm = g2d.getFontMetrics();
int textWidth = fm.stringWidth(text);
int textXPos = (bounds.x + (internalPadding / 2)) + ((bounds.width - internalPadding - textWidth) / 2);
int textYPos = bounds.y + (internalPadding / 2);
RoundRectangle2D buttonBackground = new RoundRectangle2D.Double(bounds.x, bounds.y, bounds.width, bounds.height, 20, 20);
g2d.setColor(Color.BLUE.darker());
g2d.fill(buttonBackground);
g2d.setColor(Color.WHITE);
g2d.drawString(text, textXPos, textYPos + fm.getAscent());
}
#Override
public void mouseClicked(MouseEvent e) {
if (menuBounds == null) {
return;
}
for (int index = 0; index < menuOptions.length; index++) {
if (menuBounds[index].contains(e.getPoint())) {
switch (index) {
case 0:
observer.startNewGame();
break;
case 2:
observer.randomGame();
break;
case 3:
observer.loadGame();
break;
case 4:
observer.saveGame();
break;
}
}
}
}
}
}
I am creating a music player program.
I have created the seek bar using JSlider
Code:
JSlider seek = new JSlider(JProgressBar.HORIZONTAL);
seek.setOpaque(true);
seek.setMajorTickSpacing(0);
seek.setMinorTickSpacing(0);
seek.setBackground(Color.DARK_GRAY);
seek.setSize(100, 13);
seek.setLocation(6, 30);
Currently, it looks like this :
I can only change the background of JSlider using setBackground() method.
I don't have any idea about how to change the thumb colour, thumb shape, track colour, etc.
I want my seek bar to look something like this :
How can I achieve this?
If not possible with JSlider, is it possible to create a JProgressBar which has a slidable thumb?
As was already mentioned in the comments there is no way for you to change the appearance of the slider without extending an existing implementation of SliderUI. Here is an example implementation of how one could achieve the visuals from your demo picture.
Note that hard coding the sizes and colours isn't the best approach and for a real implementation should be handled by setting and using values available by the UIManager.
class Scratch {
public static void main(final String[] args) {
SwingUtilities.invokeLater(() -> {
JPanel content = new JPanel(new BorderLayout());
content.setPreferredSize(new Dimension(300, 100));
JSlider slider = new JSlider() {
#Override
public void updateUI() {
setUI(new CustomSliderUI(this));
}
};
content.add(slider);
JFrame frame = new JFrame();
frame.setContentPane(content);
frame.pack();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
});
}
private static class CustomSliderUI extends BasicSliderUI {
private static final int TRACK_HEIGHT = 8;
private static final int TRACK_WIDTH = 8;
private static final int TRACK_ARC = 5;
private static final Dimension THUMB_SIZE = new Dimension(20, 20);
private final RoundRectangle2D.Float trackShape = new RoundRectangle2D.Float();
public CustomSliderUI(final JSlider b) {
super(b);
}
#Override
protected void calculateTrackRect() {
super.calculateTrackRect();
if (isHorizontal()) {
trackRect.y = trackRect.y + (trackRect.height - TRACK_HEIGHT) / 2;
trackRect.height = TRACK_HEIGHT;
} else {
trackRect.x = trackRect.x + (trackRect.width - TRACK_WIDTH) / 2;
trackRect.width = TRACK_WIDTH;
}
trackShape.setRoundRect(trackRect.x, trackRect.y, trackRect.width, trackRect.height, TRACK_ARC, TRACK_ARC);
}
#Override
protected void calculateThumbLocation() {
super.calculateThumbLocation();
if (isHorizontal()) {
thumbRect.y = trackRect.y + (trackRect.height - thumbRect.height) / 2;
} else {
thumbRect.x = trackRect.x + (trackRect.width - thumbRect.width) / 2;
}
}
#Override
protected Dimension getThumbSize() {
return THUMB_SIZE;
}
private boolean isHorizontal() {
return slider.getOrientation() == JSlider.HORIZONTAL;
}
#Override
public void paint(final Graphics g, final JComponent c) {
((Graphics2D) g).setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
super.paint(g, c);
}
#Override
public void paintTrack(final Graphics g) {
Graphics2D g2 = (Graphics2D) g;
Shape clip = g2.getClip();
boolean horizontal = isHorizontal();
boolean inverted = slider.getInverted();
// Paint shadow.
g2.setColor(new Color(170, 170 ,170));
g2.fill(trackShape);
// Paint track background.
g2.setColor(new Color(200, 200 ,200));
g2.setClip(trackShape);
trackShape.y += 1;
g2.fill(trackShape);
trackShape.y = trackRect.y;
g2.setClip(clip);
// Paint selected track.
if (horizontal) {
boolean ltr = slider.getComponentOrientation().isLeftToRight();
if (ltr) inverted = !inverted;
int thumbPos = thumbRect.x + thumbRect.width / 2;
if (inverted) {
g2.clipRect(0, 0, thumbPos, slider.getHeight());
} else {
g2.clipRect(thumbPos, 0, slider.getWidth() - thumbPos, slider.getHeight());
}
} else {
int thumbPos = thumbRect.y + thumbRect.height / 2;
if (inverted) {
g2.clipRect(0, 0, slider.getHeight(), thumbPos);
} else {
g2.clipRect(0, thumbPos, slider.getWidth(), slider.getHeight() - thumbPos);
}
}
g2.setColor(Color.ORANGE);
g2.fill(trackShape);
g2.setClip(clip);
}
#Override
public void paintThumb(final Graphics g) {
g.setColor(new Color(246, 146, 36));
g.fillOval(thumbRect.x, thumbRect.y, thumbRect.width, thumbRect.height);
}
#Override
public void paintFocus(final Graphics g) {}
}
}
Result:
I'm having trouble loading a sprite into a game. I have the background done, but the sprite doesn't seem to be on the background. I want to assume you put it in the main constructor, but I don't think it will work.
package MyGamePKG;
import java.awt.BorderLayout;
public class GameJFrame extends JFrame {
private JPanel contentPane;
private int x,y,x_vel,y_vel;
/**
* Launch the application.
*/
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
public void run() {
try {
GameJFrame frame = new GameJFrame();
frame.setVisible(true);
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
/**
* Create the frame.
*/
public GameJFrame() {
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setBounds(100, 100, 720, 520);
contentPane = new JPanel();
contentPane.setBorder(new EmptyBorder(5, 5, 5, 5));
setContentPane(contentPane);
contentPane.setLayout(null);
GameJPanel Jpanel = new GameJPanel();
Jpanel.setBounds(0, 0, 720, 480);
contentPane.add(Jpanel);
Jpanel.setLayout(null);
JLabel lblNewLabel = new JLabel("New label");
lblNewLabel.setIcon(new ImageIcon(GameJFrame.class.getResource("/Resources/gamemap.png")));
lblNewLabel.setBounds(0, 0, 720, 480);
contentPane.add(lblNewLabel);
}
}
The JPanel class
package MyGamePKG;
import java.awt.BasicStroke;
public class GameJPanel extends JPanel implements ActionListener, KeyListener {
private int frameRate = 30;
private int x=0;
private int y=460;
private int x_vel, y_vel;
private Image map;
private Image character;
/**
* Create the panel.
*/
public GameJPanel() {
character = new ImageIcon(getClass().getResource("/resources/sprite-concepts.png")).getImage();
this.addKeyListener(this);
this.setFocusable(true);
Timer timer = new Timer(30, this);
timer.start();
}
#Override
public void paintComponent(Graphics g)
{
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
RenderingHints rh = new RenderingHints( RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setRenderingHints(rh);
this.setOpaque(false);
myDrawBoxOvalsandRect(x,y,100,g2d);
//myDrawArc(x+25,y+40,50,50,550,170,g2d);
} // paintComponent
public void myDrawBoxOvalsandRect( int x, int y, int scale, Graphics my_g)
{
my_g.setColor(Color.cyan);
my_g.fillOval(x, y, 15, 15); //face
}
public void myDrawArc(int x, int y, int height, int width, int angle1, int angle2, Graphics my_g)
{
my_g.setColor(Color.red);
my_g.drawArc(x, y, 50, 50, 550, 170); //happy face
}
#Override
public void keyPressed(KeyEvent e) {
// TODO Auto-generated method stub
int c = e.getKeyCode();
if(c == KeyEvent.VK_LEFT)
{
x_vel = -2;
y_vel = 0;
}
if(c == KeyEvent.VK_UP)
{
x_vel = 0;
y_vel = -2;
}
if( c == KeyEvent.VK_RIGHT)
{
x_vel = 2;
y_vel = 0;
}
if( c == KeyEvent.VK_DOWN)
{
x_vel = 0;
y_vel = 2;
}
repaint();
}
#Override
public void keyReleased(KeyEvent e) {
// TODO Auto-generated method stub
x_vel = 0;
y_vel = 0;
repaint();
}
#Override
public void keyTyped(KeyEvent e) {
// TODO Auto-generated method stub
}
#Override
public void actionPerformed(ActionEvent e) {
// TODO Auto-generated method stub
if(x < 0)
{
x_vel = 0;
x = 0;
}
if(x > 720)
{
x_vel = 0;
x = 720;
}
if(y < 0)
{
y_vel = 0;
y = 0;
}
if(y > 480)
{
y_vel = 0;
y = 480;
}
x = x + x_vel;
y = y + y_vel;
repaint();
}
}
I suspect a number of things...
It's possible that the z-ordering of the components is rendering the background over the foreground components
The reliance on null layouts means that it's possible the window borders are taking up more space then you are allowing for, covering up portions of the content.
Don't rely on a null layout and guess work. Each platform/Look and Feel has different requirements when it comes to the frame border (amongst other things). Instead, you should consider...
Overriding getPreferredSize to provide better sizing hints
Use an OverlayLayout manager
Use JFrame#pack to pack the frame around the content instead of using setBounds
Consider painting the entire game state using a single paintComponent method, this would mean rendering the background, middleground and foreground within GameJPanel's paintComponent.
The benefit of this, is you gain complete control over the z-ordering process.
You should consider using key bindings over using KeyListener as it does not suffer from the same focus related issues and provides you with finer control over determining the level of focus that the component will raise key events
Any ideas why my oval is not moving to the right? I have a game loop but somehow the circle stays where it appears, instead of moving by 2pixels to the right every cycle.
The strange thing is that it enters the loop before actually painting. But then the graphics object show up(at a fixed position).
public class GamePanel extends JPanel implements Runnable {
public static int WIDTH = 1024;
public static int HEIGHT = WIDTH / 16 * 9;
private Thread t1;
boolean running;
private int FPS = 60;
private long optimalTime = 1000 / FPS;
private int heroX = 200;
private int heroY = 200;
public void addNotify(){
Dimension size = new Dimension(WIDTH,HEIGHT);
setPreferredSize(size);
setFocusable(true);
requestFocus();
running = true;
t1 = new Thread(this);
t1.start();
}
public void paintComponent (Graphics g){
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
g2.setColor(Color.WHITE);
g2.fillRect(0, 0, WIDTH, HEIGHT);
g2.setColor(Color.BLACK);
g2.fillOval(heroX, heroY, 50, 50);
g2.dispose();
}
public void run() {
long startTime;
long passedTime;
long waitTime;
while (running){
startTime = System.nanoTime();
System.out.println("Runs");
update();
draw();
repaint();
passedTime = System.nanoTime() - startTime;
waitTime = optimalTime - passedTime / 1000000;
try {
if (waitTime <= 0){
waitTime = 2;
}
Thread.sleep(waitTime);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
private void draw() {
}
private void update() {
heroX += 2;
}
}
You should use a swing Timer instead of trying to use threads. If you need to do something time consuming in the background, use SwingWorkers. It is possible to use your own threading with swing should you really need to (you need a twisted user case to need to do that), but do not try to do that until you have a good grasp of threads and the way to use them with swing.
public class GamePanel extends JPanel {
private static final int DELAY = 1000 / 60;
private final Timer timer;
// ...
private int heroX = 200;
private int heroY = 200;
public GamePanel() {
timer = new Timer(DELAY, new ActionListener() {
#Override
public void actionPerformed(final ActionEvent e) {
update();
repaint();
}
});
// ...
}
// No need to make this public
#Override
protected void paintComponent (Graphics g) {
// ...
}
#Override
public Dimension getPreferredSize() {
// Overriding is cleaner than using set*Size(). Search old questions to see why
}
public void startAnimation() {
timer.start();
}
public void stopAnimation() {
timer.stop();
}
// Remove addNotify() and run()
}
After reading and testing, I just wanted to do this. It works as intended, even if paintComponent is outside the EDT.
public class GamePanel extends JPanel implements Runnable {
public static int WIDTH = 1024;
public static int HEIGHT = WIDTH / 16 * 9;
private int cordX = WIDTH / 2;
private Thread t1;
private boolean running = true;
public void addNotify() {
super.addNotify();
Dimension size = new Dimension (WIDTH, HEIGHT);
setPreferredSize(size);
t1 = new Thread(this);
t1.start();
}
public void paintComponent(Graphics g) {
g.setColor(Color.WHITE);
g.fillRect(0, 0, WIDTH, HEIGHT);
g.setColor(Color.BLACK);
g.fillOval(cordX, HEIGHT /2 , 20, 20);
}
public void run() {
while(running) {
cordX += 2;
repaint();
try {
Thread.sleep(17);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
I am trying to design a simple game using Graphics2D in a JPanel. I am able to draw normal objects by overriding the paintComponent() method. But when I reference the Graphics2D object inside a orphan Thread, it does not work. Where am I going wrong?
public void paintComponent(Graphics g) {
super.paintComponent(g);
g2d = (Graphics2D) g;
g2d.drawString("sample",60,100); //Works fine
if(<Certain Condition>){
new Thread(new Runnable(){
//Some Code Here
public void run() {
try{
g2d.drawString("sample2",60,100); //Does not work.. :(
System.out.println("Test Print"); //Shows Output
}
catch (Exception e)
{
}
}
}).start();
}
}
Here is the complete code for reference. This is essentially a 'ping pong ball' game. Its working well but I am not able to highlight an increase in score when the ball hits the striker. The important part of code is highlighted. It's SSCCE.
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import java.util.Random;
public class MovingBall extends JPanel {
int XPos, YPos;
int speedX, speedY;
int diameter;
private JButton jButton1 = new JButton();
private JButton jButton2 = new JButton();
private JLabel jLabel1 = new JLabel();
private static Timer timer;
private static MovingBall movingball;
private int w,h;
private int strikerHeight;
private int strikerWidth;
private int score;
private boolean isBallMoving;
int strikerYPos;
Graphics2D g2d;
public MovingBall() {
//Striker Properties
strikerHeight = 100;
strikerWidth = 20;
strikerYPos = strikerHeight/2;
//Ball Properties
isBallMoving = false;
XPos = strikerWidth + 5;
YPos = 0;
Random r = new Random();
speedX = 2+ Math.abs(r.nextInt()) % 5;
speedY = 2+ Math.abs(r.nextInt()) % 5;
diameter = 50;
//UI Objects
try {
jbInit();
} catch (Exception e) {
e.printStackTrace();
}
movingball = this; //Helps to access the current class object in inner classes
//Create a timer for animation
timer = new Timer(1, new ActionListener() {
public void actionPerformed(ActionEvent evt) {
movingball.repaint();
}
});
timer.start();
}
public void paintComponent(Graphics g) {
super.paintComponent(g);
g2d = (Graphics2D) g;
Dimension size = getSize();
Insets insets = getInsets();
w = size.width - insets.left - insets.right;
h = size.height - insets.top - insets.bottom;
//Paint the striker
g2d.setColor(Color.DARK_GRAY);
if(strikerYPos < strikerHeight/2) //Top End
g2d.fillRect(0,0, strikerWidth, strikerHeight);
else if(strikerYPos > (h-strikerHeight/2)) //Bottom End
g2d.fillRect(0,h-strikerHeight, strikerWidth, strikerHeight);
else //Anywhere in the middle
g2d.fillRect(0,strikerYPos - (strikerHeight/2), strikerWidth, strikerHeight);
//Paint the ball
if (isBallMoving) {
XPos += speedX;
YPos += speedY;
g2d.drawOval(XPos, YPos, diameter,diameter);
if((XPos+diameter) >= w)
{
//speedX *= -1;
speedX = ((int)Math.signum((double)speedX))*(-1) * (2+ Math.abs(new Random().nextInt()) % 5);
XPos = w-diameter-1;
}
if(XPos <= strikerWidth)
{
if((YPos+diameter/2) >= (strikerYPos-strikerHeight/2) && (YPos+diameter/2) <= (strikerYPos+strikerHeight/2))
{
score++;
//////////////////////////////////////////////////////////////////////
/////THIS IS THE PART TO FOCUS ON///////////////////////////////////////
/////WHEN THE BALL HITS THE STRIKER, I SHOW A '+1' TEXT FADING UPWARDS FROM THE POINT OF HIT
/////(THIS IS TO HIGHLIGHT A +1 INCREASE IN SCORE)///////////////////
//////NOW SINCE THE BALL MAY HIT THE STRIKER AGAIN BEFORE THE PREVIOUS +1 HAS COMPLETELY FADED,
//////I HAVE MADE THIS SIMPLE THREAD TO CREATE A +1 EVERY TIME THERE IS A HIT. SO THERE CAN BE MULTIPLE
//////+1 ON THE SCREEN.
//-------------------------------SADLY, SOMETHING IS WRONG-------------------
//Print a '+1' to show score increase
new Thread(new Runnable(){
int yStart = strikerYPos;
int fadeLength = 0;
Timer pointTimer;
int MAX_FADE_LEN = 50;
public void run() {
try
{
pointTimer = new Timer(1, new ActionListener() {
public void actionPerformed(ActionEvent evt) {
if(fadeLength >= MAX_FADE_LEN)
pointTimer.stop();
g2d.setColor(new Color(0,0,0,255));
g2d.setFont(new Font("Times",Font.BOLD,20));
g2d.drawString("+1",60,yStart - fadeLength);
g2d.drawOval(100,100,50,50);
System.out.println("Drawn +1 at x = " + 60 + " y = " + (yStart - fadeLength));
fadeLength++;
}
});
pointTimer.start();
}
catch (Exception e)
{
}
}
}).start();
////////////////THREAD ENDS HERE//////////////////////
}
else
{
score--;
}
//SHOW THE SCORE ON THE LABEL
jLabel1.setText("Score: " + score);
speedX = ((int)Math.signum((double)speedX))*(-1) * (2+ Math.abs(new Random().nextInt()) % 5);
XPos = strikerWidth+1;
}
if(YPos <= 0)
{
speedY = ((int)Math.signum((double)speedY))*(-1) * (2+ Math.abs(new Random().nextInt()) % 5);
YPos = 0;
}
if((YPos+diameter) >= h)
{
speedY = ((int)Math.signum((double)speedY))*(-1) * (2+ Math.abs(new Random().nextInt()) % 5);
YPos = h-diameter;
}
} else {
g2d.drawOval(XPos,YPos,diameter,diameter);
return;
}
}
public static void main(String[] args) {
JFrame frame = new JFrame("Magic Ball");
movingball = new MovingBall();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(movingball);
frame.setSize(450, 700);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
private void jbInit() throws Exception {
jButton1.setText("Start");
jButton1.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
jButton1_actionPerformed(e);
}
});
jButton2.setText("Stop");
jButton2.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
jButton2_actionPerformed(e);
}
});
jLabel1.setText("Score:0");
this.add(jButton1, null);
this.add(jButton2, null);
this.add(jLabel1, null);
this.setBackground(Color.white);
this.addMouseMotionListener(new MouseMotionListener() {
public void mouseMoved(MouseEvent e) {
int coordX = e.getX();
if(coordX < 200)
strikerYPos = e.getY();
}
public void mouseDragged(MouseEvent e) {
}
});
}
private void jButton1_actionPerformed(ActionEvent e) {
if(!isBallMoving)
isBallMoving = true;
}
private void jButton2_actionPerformed(ActionEvent e) {
isBallMoving = false;
}
}
everything inside paintComponent is repainted (automatically) on every mouse, key and internall methods implemented in API, then you thread probably never ended, there can be bunch of concurently Threads, nothing is repainted, displayed
output to the Swing GUI must be done on EDT
use Swing Timer instead of new Thread(new Runnable(){
call repaint()
I don't think many people would consider almost 250 LOC to be 'short' (though I must admit I was deliberately vague when writing the SSCCE document). OTOH I adapted my shorter source seen here to an animated example that shows a 'fade effect' on mouse clicks. Adapting it to your needs is left as an exercise for ..you.
This source shows how to change the drawn string over a period of 5 seconds. It uses the same Thread (the EDT) for both the main (bouncing ball) and fade animation.
import java.awt.*;
import java.awt.event.*;
import java.awt.geom.*;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import javax.swing.*;
class ShapeCollision {
private BufferedImage img;
private Area walls;
int x;
int y;
int xDelta = 3;
int yDelta = 2;
ArrayList<Strike> strikes;
/**
* A method to determine if two instances of Area intersect
*/
public boolean doAreasCollide(Area area1, Area area2) {
boolean collide = false;
Area collide1 = new Area(area1);
collide1.subtract(area2);
if (!collide1.equals(area1)) {
collide = true;
}
Area collide2 = new Area(area2);
collide2.subtract(area1);
if (!collide2.equals(area2)) {
collide = true;
}
return collide;
}
ShapeCollision() {
int w = 400;
int h = 200;
img = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
final JLabel imageLabel = new JLabel(new ImageIcon(img));
x = w / 2;
y = h / 2;
strikes = new ArrayList<Strike>();
MouseListener strikeListener = new MouseAdapter() {
#Override
public void mouseClicked(MouseEvent e) {
Strike s = new Strike(e.getPoint(),System.currentTimeMillis());
strikes.add(s);
}
};
imageLabel.addMouseListener(strikeListener);
walls = new Area(new Rectangle(0, 0, w, h));
ActionListener animate = new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
animate();
imageLabel.repaint();
}
};
Timer timer = new Timer(50, animate);
timer.start();
JOptionPane.showMessageDialog(null, imageLabel);
timer.stop();
}
public void animate() {
Graphics2D g = img.createGraphics();
g.setRenderingHint(
RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g.setColor(Color.BLACK);
g.fillRect(0, 0, img.getWidth(), img.getHeight());
x += xDelta;
y += yDelta;
int s = 15;
Area player = new Area(new Ellipse2D.Double(x, y, s, s));
// Acid test of edge collision;
if (doAreasCollide(player, walls)) {
if (x + s > img.getWidth() || x < 0) {
xDelta *= -1;
}
if (y + s > img.getHeight() || y < 0) {
yDelta *= -1;
}
}
g.setColor(Color.ORANGE);
g.setColor(Color.YELLOW);
g.fill(player);
for (Strike strike : strikes) {
strike.draw(g);
}
g.dispose();
}
public static void main(String[] args) {
Runnable r = new Runnable() {
#Override
public void run() {
new ShapeCollision();
}
};
// Swing GUIs should be created and updated on the EDT
// http://docs.oracle.com/javase/tutorial/uiswing/concurrency/initial.html
SwingUtilities.invokeLater(r);
}
}
class Strike {
private Point point;
private long started;
private final long DURATION = 5000;
private boolean expired = false;
Strike(Point point, long time) {
this.point = point;
started = time;
}
public void draw(Graphics g) {
long now = System.currentTimeMillis();
long age = now - started;
if (age>DURATION) {
expired = true;
return;
}
double fraction = 1d-((double)age/(double)DURATION);
int alpha = (int)(fraction*255d);
Color c = new Color(255,255,255,alpha);
g.setColor(c);
String s = point.x + "," + point.y;
g.drawString( s, point.x, point.y );
}
public boolean isExpired() {
return expired;
}
}
As i understand - you save the Graphics2D object into g2d variable and trying to paint something onto it from a separate thread later? If so - don't do it. It is a really bad thing to do. Really.
If you want to modify (animate/change) whatever is painted on your component - simply change the data/model which affects the painting and than repaint the whole component or its modified part (any rectangle within the component bounds).
For example in your example case - keep painted string coordinates outside the paint method and modify them in a separate thread and then just call repaint on the component each time you change them. With each repaint string will be painted at the updated coordinates.
Also note that repaint might be called outside of the EDT (Event Dispatch Thread) as it will perform the actual repaint in EDT anyway.
Here is some random example of animation:
public class AnimationTest
{
private static List<Point> locationData = new ArrayList<Point> ();
private static List<Boolean> directionData = new ArrayList<Boolean> ();
public static void main ( String[] args )
{
locationData.add ( new Point ( 5, 25 ) );
directionData.add ( true );
final JComponent canvas = new JComponent ()
{
protected void paintComponent ( Graphics g )
{
super.paintComponent ( g );
Graphics2D g2d = ( Graphics2D ) g;
for ( int i = 0; i < locationData.size (); i++ )
{
Point p = locationData.get ( i );
g2d.drawString ( "Some string #" + i, p.x, p.y );
}
}
};
canvas.addMouseListener ( new MouseAdapter ()
{
public void mousePressed ( MouseEvent e )
{
locationData.add ( e.getPoint () );
directionData.add ( true );
canvas.repaint ();
}
} );
JFrame frame = new JFrame ();
frame.getContentPane ().setLayout ( new BorderLayout () );
frame.getContentPane ().add ( canvas );
frame.setSize ( 500, 500 );
frame.setLocationRelativeTo ( null );
frame.setDefaultCloseOperation ( JFrame.EXIT_ON_CLOSE );
frame.setVisible ( true );
Timer timer = new Timer ( 1000 / 48, new ActionListener ()
{
public void actionPerformed ( ActionEvent e )
{
for ( int i = 0; i < locationData.size (); i++ )
{
Point p = locationData.get ( i );
if ( directionData.get ( i ) )
{
if ( p.y < canvas.getHeight () - 1 )
{
p.y += 1;
}
else
{
directionData.set ( i, false );
}
}
else
{
if ( p.y > 20 )
{
p.y -= 1;
}
else
{
directionData.set ( i, true );
}
}
}
canvas.repaint ();
}
} );
timer.start ();
}
}
You can find here:
Data lists on which painting and animation are based with single initial element
Data modification through mouse interaction
Proper canvas update on any data changes
Example is not too optimized, but should be enough to understand the concept.
Print the graphicsobject, if it changes between calls in paintComponent it means the one you are passing to the orphan thread is a dead object which swing doesn't use anymore for painting on the current component.
Swing is a platform-independent, Model-View-Controller GUI framework for Java, which follows a single-threaded programming model.
You should use dispatcher. http://en.wikipedia.org/wiki/Event_Dispatch_Thread