I am animating a series of images in java. So far I able to animate without any problem. The problem occur only when I add in control(Start, Stop, etc..). When I press Start in my GUI the GUI does not show the animation only the last frame is shown after the animation has ended.
I not sure is it the threading problem or a painting problem, as I have tried a few methods and none of them worked.
Following is my code:
public class SwingAnimation extends JPanel implements ActionListener {
protected JFrame frame;
protected JLabel lblDisplay;
protected JButton BtnStart, BtnStop, BtnPause;
protected JCheckBox chkLoop;
protected Thread th;
public static void main(String[] args) {
SwingAnimation sa = new SwingAnimation();
}
public SwingAnimation() {
frame = new JFrame("Animation");
Panel panel = new Panel();
lblDisplay = new JLabel();
BtnStart = new JButton("Start");
BtnStop = new JButton("Stop");
BtnPause = new JButton("Pause");
chkLoop = new JCheckBox("Loop");
BtnStop.setEnabled(false);
BtnPause.setEnabled(false);
BtnStart.setActionCommand("start");
BtnStop.setActionCommand("stop");
BtnPause.setActionCommand("pause");
panel.add(lblDisplay);
panel.add(BtnStart);
panel.add(BtnPause);
panel.add(BtnStop);
panel.add(chkLoop);
frame.add(panel, BorderLayout.CENTER);
frame.setSize(400, 400);
frame.setVisible(true);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
//set the frame in the center of the screen
Dimension screensize = Toolkit.getDefaultToolkit().getScreenSize();
int screen_x = (screensize.width - frame.getWidth()) / 2;
int screen_y = (screensize.height - frame.getHeight()) / 2;
frame.setLocation(screen_x, screen_y);
BtnStart.addActionListener(this);
BtnStop.addActionListener(this);
BtnPause.addActionListener(this);
chkLoop.addActionListener(this);
th = new Thread();
//ImageAnimator();
}
public void ImageAnimator() {
try {
for (int i = 0; i <= 299; i++) {
ImageIcon images = new ImageIcon("C:\\Users\\Desktop\\Images\\Snap" + i + ".jpg");
lblDisplay.setIcon(images);
th.sleep(25);
}
} catch (InterruptedException e) {
}
}
public void actionPerformed(ActionEvent e) {
if ("start".equals(e.getActionCommand())) {
BtnStart.setEnabled(false);
BtnStop.setEnabled(true);
BtnPause.setEnabled(true);
lblDisplay.setVisible(true);
ImageAnimator();
} else if ("stop".equals(e.getActionCommand())) {
BtnStart.setText("Start");
BtnStart.setEnabled(true);
BtnStop.setEnabled(false);
BtnPause.setEnabled(false);
lblDisplay.setVisible(false);
th = null;
} else if ("pause".equals(e.getActionCommand())) {
BtnStart.setText("Resume");
BtnStart.setEnabled(true);
BtnStop.setEnabled(true);
BtnPause.setEnabled(false);
}
}
}
Quick Fix:
Change ImageAnimator() like this:
lblDisplay.setIcon(images);
lblDisplay.paintImmediately(getBounds());
th.sleep(25);
The problem you're having is that setting an icon does not automatically cause Swing to repaint the component onscreen. Calling paintImmediately will do that.
General advice:
Animation in swing is normally done using the Swing Timer. You'll notice at the moment that your UI is unresponsive - once you've started the animation, you won't be able to stop it until its over. That's because all Swing events happen on a single thread - the Event Dispatch Thread. Running a loop with Thread.sleep(...) in the middle ties this thread up, leaving it unavailable to process other input (such as pressing the stop button)
This article helped me immeasurably when I was trying to understand how Swing handles concurrency, and The Swing Trail has lots of advice on using Swing effectively, painting custom components etc.
I'd also plug the demo code on Filthy Rich Clients, I'm working through the book at the moment and it's worth a look.
This Java 2D games tutorial covers several basic animation techniques. Some related example that illustrate the techniques are cited here, and this complete example illustrates stop and start buttons.
Related
So I have this method that takes a JFrame, and two JPanels as arguments, and makes a fadeout/fadein transition from one Jpanel to another. All I do is add a new Jpanel on the first Jpanel on the parameter and keep updating its background color in a loop until it becomes black from transparent. The first half works fine until it comes to second half where I try to run a while loop where it keeps looping until the background color of the tint panel on the 2nd Jpanel becomes transparent from black. It stays black the whole time.
Any thoughts on this? What am I doing wrong?
public static void fadeShow(JFrame frame, JPanel panel1, JPanel panel2) {
new Thread(new Runnable() {
#Override
public void run() {
try {
JPanel tint = new JPanel();
tint.setOpaque(true);
tint.setBackground(new Color(0,0,0,0));
tint.setBounds(frame.getBounds().x+1, frame.getBounds().y, frame.getWidth(), frame.getHeight());
panel1.add(tint);
Thread.sleep(1500);
int i = 5;
while(i <= 255) {
tint.setBackground(new Color(0,0,0,i));
i+=25;
Thread.sleep(50);
}
panel1.setVisible(false);
tint.setVisible(false);
panel1.remove(tint);
tint = new JPanel();
tint.setOpaque(true);
tint.setBackground(new Color(0,0,0,255));
tint.setBounds(frame.getBounds().x+1, frame.getBounds().y, frame.getWidth(), frame.getHeight());
panel2.add(tint);
frame.add(panel2);
i = 250;
while(i > 0) {
tint.setBackground(new Color(0,0,0,i));
i-=25;
Thread.sleep(50);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
Your program flouts Swing threading rules by making significant Swing gui state changes from within a background thread. This risks freezing your GUI or causing strange visual artifacts. Instead use a Swing Timer, since this can allow you to make repeated calls with an interval delay, and guarantee that those calls will all be made on the Swing event thread.
I'm building a minesweeper game in Java. During the GUI design I came to a problem. Here is my game window.
The grey area supposed to contain the actual mine-field. What I want to happen is that after the player clicks Start Game, I want to load the JPanel that contains the implementation of the mine field. However, it's seems that I can't add any more JPanels after JFrame has loaded, I'm correct? I tried running the addition of the mine field in a different thread (sleep for a few seconds and then add the JPanel), just to see if it's possible, and got no result.
public GameWindow(){
super("Minesweeper");
this.setLayout(new BorderLayout());
this.gamePanel = new GamePanel(new GridLayout(BOARD_SIZE,BOARD_SIZE,1,1));
this.timerPanel = new TimerPanel(new BorderLayout(15,15));
this.timerPanel.initializeFlagLabel(8);
this.mineField = new Button[BOARD_SIZE][BOARD_SIZE];
int buttonCounter = 0;
for(int i = 0; i < BOARD_SIZE; i ++){
for(int j = 0; j < BOARD_SIZE; j++){
mineField[i][j] = new Button();
mineField[i][j].setName(Integer.toString(buttonCounter++));
mineField[i][j].setBackground(Color.GRAY);
mineField[i][j].setPreferredSize(new Dimension(10,10));
mineField[i][j].addMouseListener(this);
gamePanel.add(mineField[i][j]);
}
}
Container container = getContentPane();
container.add(timerPanel,BorderLayout.PAGE_START);
container.add(new MenuPanel(this), BorderLayout.WEST);
new Thread(new Runnable(){//Here I want to wait for the JFrame to
//load and then to add the gamePanel.
public void run(){
try {
Thread.sleep(4000);
container.add(gamePanel, BorderLayout.CENTER);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
});
this.setSize(800,640);
this.setLocationRelativeTo(null);
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setResizable(false);
this.setVisible(true);
}
To summarize my question is, is it possible to add new JPanels to JFrame during run-time, and if so how can I do it?
The short answer is, yes. You need to call revalidate and repaint to trigger a layout and paint pass on the container you've updated.
The longer answer is, probably not the way you're doing it.
Apart from the fact that you never actually start the Thread, Swing is not thread safe and you shouldn't be updating the UI out side of the context of the Event Dispatching Thread.
In you case, a alternative (and safer) solution would be to use a Swing Timer. See How to use Swing Timers for more details
In the action listener for start game, you can add a jpanel to the frame that is precreated. You also may need to repaint() or revalidate()
I am getting some strange errors when running my Java game. I've been working on this game for weeks as a project for my computer science class and it is due on 6/4/2015, so I appreciate any help. Initially I wrote the game in a class called Game and ran it from a static method called run. Later, I decided to add a GUI menu in a class called Control. To run the game now, I call the main method in the Control class. This menu has a button with an action listener. When the button is clicked, the run method in the Game class is called. If I click and run the run method directly the game works fine. But if I click the button which calls the run method it draws a frame, but not the actual game.
Here is my code for the GUI:
public class Control extends JFrame implements ActionListener {
// JPanel
public JPanel pnlButton = new JPanel();
// Buttons
public JButton btnAddFlight = new JButton("Multiplayer");
public JButton single = new JButton("Singleplayer");
public Control() throws IOException,InterruptedException {
super("Bouncy Ball");
//Set button size
btnAddFlight.setBounds(150, 400, 220, 30);
single.setBounds(150,350,220,30);
// JPanel bounds
pnlButton.setBounds(0, 0, 500, 500);
pnlButton.setBackground(Color.WHITE);
// Adding the Bouncy Ball logo to JPanel
String path = "gg.jpg";
File file = new File(path);
BufferedImage image = ImageIO.read(file);
JLabel label = new JLabel(new ImageIcon(image));
label.setBounds(179,50,150,150);
//Action Listener setup
single.addActionListener(this);
//add buttons and title logo to JPanel
pnlButton.add(btnAddFlight);
pnlButton.add(label);
pnlButton.add(single);
//Set up and add the instructions to the JPanel
JLabel gg = new JLabel("Welcome to Bouncy Ball, a game where you have to manipulate");
gg.setFont(new Font("Serif", Font.PLAIN, 18));
gg.setBounds(0,10,500,500);
pnlButton.add(gg);
JLabel f = new JLabel("the window size with WASD to win! Click the buttons to start.");
f.setFont(new Font("Serif", Font.PLAIN, 18));
f.setBounds(0,28,500,500);
pnlButton.add(f);
pnlButton.setLayout(null);
//Add JPanel to JFrame
this.add(pnlButton);
// JFrame properties
setSize(500, 500);
setTitle("Bouncy Ball");
setLocationRelativeTo(null);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setVisible(true);;
}
#Override
public void actionPerformed(ActionEvent submitClicked) {
try{
isClicked = true;
Game.run();
}
catch(InterruptedException a){}
}
public static void main(String[] args) throws IOException,InterruptedException{
new Control();
}
}
and here is the run method in my game class:
public static void run() throws InterruptedException {
//Setting up JFrame
frame = new KeyFrame("Bouncy Ball");
frame.setSize(1000, 1000);
//Setting up Scanner and get user level input
Scanner scan = new Scanner (System.in);
System.out.println("Level one, two, three, or four?(must be an int, four is multiplayer)");
f = scan.nextInt();
//Display JFrame
frame.setVisible(true);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.toFront();
//Add Game JPanel
game = new Game();
frame.add(game);
//Setting up game basics
b = new Ball(game,0,0,30,30);
setUpLevel();
objList.add(0,b);
//Main Game Loop
while (true) {
//Request Focus
game.requestFocusInWindow();
//Update current Time
lastStartTime = System.currentTimeMillis();
//Move the ball and the player
p.move();
b.move();
//Refresh JPanel
game.repaint();
Thread.sleep(10);
//Check if the player wins or loses
checkWin();
checkLoss();
}
}
If I call the run method directly it works, but not if I click the button in the GUI. I have a feeling that it is a thread issue, because the main thread ends once run is called. I'm not a 100% percent sure as to the cause though. I'd appreciate any responses, I've been working on this game for weeks as a project for my computer science class and it is due on 6/4/2015.
Swing is single threaded - all painting, events, etc...occur on this thread (named the EDT). The Game.run method is called on the EDT, which in turn executing a long running task (eg while (true), Thread.sleep) - this prevents the EDT from doing anything else. To perform animation, consider using a Thread, or better yet a Swing Timer.
I have a maybe simple question. Obviously because my program does not do what it is supposed to...
First of all my code:
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.plaf.nimbus.NimbusLookAndFeel;
public class Timer
extends JFrame
implements ActionListener
{
protected class TThread extends Thread{
private boolean running = false;
#Override
public void run() {
int timer = 10,
index = 0;
running = true;
while(running){
try {
out.setText(timer + " Secs");
timer--;
if(timer == 0){
if(index % 2 == 0){
timer = ti1;
out.setBackground(Color.red);
}else{
timer = ti2;
out.setBackground(Color.green);
}
index++;
}
sleep(1000L);
} catch (InterruptedException e) {
}
}
}
#Override
public void interrupt() {
running = false;
}
}
private static final long serialVersionUID = 1L;
private JTextField t1 = new JTextField(),
t2 = new JTextField();
private int ti1 = 0, ti2 = 0;
private JLabel l1 = new JLabel("Zeit 1"),
l2 = new JLabel("Zeit 2"),
out = new JLabel("00 Secs", SwingConstants.CENTER);
private JButton go = new JButton("Go"),
stop = new JButton("Stop");
private JPanel cont = new JPanel();
private TThread tr = new TThread();
public Timer() {
super("Timer");
setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
setSize(800, 600);
setLayout(null);
add(cont);
cont.setBounds(0, 0, getWidth(), 200);
cont.setLayout(new GridLayout(3, 2));
cont.add(l1);
cont.add(t1);
cont.add(l2);
cont.add(t2);
cont.add(go);
go.addActionListener(this);
cont.add(stop);
stop.addActionListener(this);
add(out);
out.setBounds(0, 200, getWidth(), getHeight()-200);
out.setFont(new Font("Arial", Font.BOLD, 72));
try {
UIManager.setLookAndFeel(new NimbusLookAndFeel());
SwingUtilities.updateComponentTreeUI(this);
} catch (UnsupportedLookAndFeelException e) {
}
}
public static void main(String[] args) {
Timer t = new Timer();
t.setVisible(true);
}
#Override
public void actionPerformed(ActionEvent e) {
if(e.getSource().equals(go)){
ti1 = Integer.parseInt(t1.getText());
ti2 = Integer.parseInt(t2.getText());
tr.run();
}else if(e.getSource().equals(stop)){
tr.interrupt();
}
}
}
So back to my problem:
If I run the program and hit the 'Go'-Button after entering some numbers the program gets stuck. I think the issue is caused by the while-loop in TThread.
It's quite a time since I last used Threads and now i searched for a long time and nothing worked for me...
Hopefully someone can tell me what the issue is and can give a solution or some hints how to solve the problem.
Greetings
max.
You never run the thread in a background thread by calling start() on it. Instead you call run() which runs it on the current thread and not in a background thread. To solve this, call start() on your Thread object, not run().
so not:
tr.run();
but rather:
tr.start();
Other issues:
You're generally better off having classes implement Runnable rather than extend Thread.
You're trying to make Swing calls from a background thread which shouldn't be done. Almost all Swing method calls should be done on the Swing event thread or EDT. For more on this, please see: Concurrency in Swing.
One of your class names, Timer, is the same as a critical Swing class, the javax.swing.Timer class. I'd rename your class to avoid confusion, especially if you would want to use a Swing Timer.
And in fact on view of your code, I think that you would be much better off using a Swing Timer instead of a background Thread for your application. This is a much easier way to do your timer animation and makes it much easier to ensure that Swing calls are made on the Swing event thread. Please check the Swing Timer tutorial.
You are using null layout. While to a newbie a null layout often appears to be the best way to easily create complex GUI's, as you gain more Swing GUI experience you will find that actually the opposite is true. If you use layout managers, often nesting JPanels, each with its own layout manager, you can more easily create complex, beautiful GUI's, that easily resize, that look good on all platforms, and that are much easier to maintain, debug and enhance.
I'm trying to make a little game on Java using the Swing components (Boggle-type game).
The way I have it set up right now, it basically opens up to the game right away - but I want to have a start up window with two buttons - "Tutorial" and "Play". I already have the functionality (my Tutorial button just opens a new Window with all the things on it) I'm just not sure how to create a second JFrame and then switch to it when I press Play (or rather, create a JFrame, then switch to the one I've already created when the JButton is pressed). I guess I could cause a new JFrame to open on the same location and the old one to become non-visible - but I was hoping for a simpler solution.
I also want to do this on completion of the game, switching again automatically to a little stat page - so any info will be appreciated.
This is what I have so far in case you guys want to see my code (I haven't yet hooked up the Enter key send the userWord to be validated and scored in my other classes, or filled in the tileGrid with Tile Objects, or the timer.... but that will all come later!)
public class Game implements Runnable {
public void run(){
final JFrame frame = new JFrame("Boggle");
frame.setLocation(500,200);
// Input - holds typing box
final JLetterField typingArea = new JLetterField(1);
typingArea.setFocusTraversalKeysEnabled(false);
typingArea.setEditable(true);
typingArea.setFocusable(true);
typingArea.requestFocusInWindow(); //also this request isn't being granted..
//if anyone could explain why i would love you
// I want the focus on the TextField on startup
frame.add(typingArea, BorderLayout.SOUTH);
typingArea.addKeyListener(new KeyAdapter() {
public void keyPressed (KeyEvent e) {
if (e.getKeyCode() == KeyEvent.VK_ENTER) { // enter key is pressed
String userWord = typingArea.getText().toLowerCase();
typingArea.setText("");
}
}
});
final JLabel status = new JLabel("Running...");
// Main playing area
GridLayout tileGrid = new GridLayout(4,4);
final JPanel grid = new JPanel(tileGrid);
frame.add(grid, BorderLayout.CENTER);
// Reset button
final JPanel control_panel = new JPanel();
frame.add(control_panel, BorderLayout.NORTH);
final ImageIcon img = new ImageIcon("Instructions.png", "My Instructions...");
final JButton info = new JButton("Help");
info.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
final JFrame infoFrame = new JFrame("Tutorial");
infoFrame.setLocation(500,50);
JLabel tutorialImg = new JLabel(img);
int w = img.getIconWidth();
int h = img.getIconHeight();
infoFrame.setSize(w, h);
infoFrame.add(tutorialImg);
infoFrame.setVisible(true);
}
});
control_panel.add(info);
// Put the frame on the screen
frame.pack();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
public static void main(String[] args){
SwingUtilities.invokeLater(new Game());
}
}
use CardLayout instead of second JFrame, your concept is heading to OutOfMemory
use JFrame.pack(after switch betweens Cards in CardLayout) if you want to change JFrames bounds on runtime,