I tried making a sprite animation in JAVA using Swing. The code below is an array of ImageIcon which I'm going to iterate through in order to show the several different images (only 2 for testing purposes).
But I don't know how to properly time each iteration. I mean, when I compile the code the label I'm working on, only displays the last image of the array (obviously), but I want it to display the first one for some ms and the other one after.
I made some research and saw some suggestions regarding Time class, but I don't really know how to implement it in these circunstances. I also tried to use sleep, which works fine in C++, but only came up with thread.sleep which doesn't work in this case.
The animation is supposed to simulate a playing card being turned around (for the Monopoly game).
Can anyone give me some input on the matter?
import javax.swing.ImageIcon;
import javax.swing.JLabel;
public class Sprite2 {
private ImageIcon[] sprites;
private ImageIcon a = new ImageIcon("C:/Users/Guilherme/Desktop/G/FEUP/2º Ano/2nd semester/Eclipse repos/Sprite/src/images/ball.jpg");
private ImageIcon b = new ImageIcon("C:/Users/Guilherme/Desktop/G/FEUP/2º Ano/2nd semester/Eclipse repos/Sprite/src/images/transferir.jpg");
public Sprite2() {
sprites = new ImageIcon[] {a, b};
}
public void render(JLabel lbl) {
for (int i = 0; i < sprites.length; i++) {
lbl.setIcon(sprites[i]);
//sleep(1000); - Looking for a similar Java function which is able to delay each iteration and make it look like a gif
}
}
You can use Swing timer, see How to Use Swing Timers for more details.
You should not use sleep() on Event Dispatch Thread as it will stop the thread from processing painting and other UI related events and the UI will become frozen. For more details see The Event Dispatch Thread tutorial.
I figured it out using timer.
For some reason, the first frame wouldn't show up in the first animation, hence sprites[0] and [1] are the same image.
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.Timer;
public class Sprite extends JPanel implements ActionListener{
private ImageIcon[] sprites = new ImageIcon[4];
private Timer time;
private int delay = 500, currentFrame = 0;
private ImageIcon a0 = new ImageIcon("C:/Users/Guilherme/Desktop/G/FEUP/2º Ano/2nd semester/Eclipse repos/Sprite/src/images/ball.jpg");
private ImageIcon a1 = new ImageIcon("C:/Users/Guilherme/Desktop/G/FEUP/2º Ano/2nd semester/Eclipse repos/Sprite/src/images/transferir.jpg");
private ImageIcon a2 = new ImageIcon("C:/Users/Guilherme/Desktop/G/FEUP/2º Ano/2nd semester/Eclipse repos/Sprite/src/images/smurf_sprite.png");
public Sprite() {
sprites[0] = a0;
sprites[1] = a0;
sprites[2] = a1;
sprites[3] = a2;
time = new Timer(delay, this);
time.start();
}
public void paintComponent(Graphics g) {
super.paintComponent(g);
sprites[currentFrame].paintIcon(this, g, 0, 0);
if (currentFrame == sprites.length-1) {
time.stop();
}
else currentFrame++;
}
public void actionPerformed(ActionEvent e) {
repaint();
}
public static void main(String[] arg) {
JFrame f = new JFrame();
Sprite s = new Sprite();
f.add(s);
f.setVisible(true);
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setSize(500,400);
}
}
Related
I have a problem in which I hope SwingWorker can help me, but I am not quite sure how to integrate it in my program.
The problem:
In a CardLayout I have a button on Card1 that opens Card2.
Card2 has a JList with a custom renderer(extending JLabel) which will display on average 1 to 6 images which are:
PNGs
around 500kb in size
loaded via imageIO with the change of cards
the renderer applies heavy operations such as image scaling or blurring and than sets the image as JLabel icon.
This can almost take up to a second if around 6 images have to be rendered, which is does not happen frequently but even that occasional split second of unresponsiveness feels bad.
Now I thought a SwingWorker might help here, but I'm thoroughly confused as to how I would have to integrate it.
Assuming we had this Code snippet
import javax.swing.*;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
public class Example {
private JPanel mainPanel = new JPanel();
private JList<Product> list = new JList();
private JScrollPane scroll = new JScrollPane();
private Map<String, Color> colorMap = new HashMap<>();
public Example() {
colorMap.put("red", Color.red);
colorMap.put("blue", Color.blue);
colorMap.put("cyan", Color.cyan);
colorMap.put("green", Color.green);
colorMap.put("yellow", Color.yellow);
mainPanel.setBackground(new Color(129, 133, 142));
scroll.setViewportView(list);
scroll.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
scroll.setPreferredSize(new Dimension(80,200));
list.setCellRenderer(new CustomRenderer());
DefaultListModel model = new DefaultListModel();
model.addElement(new Product("red"));
model.addElement(new Product("yellow"));
model.addElement(new Product("blue"));
model.addElement(new Product("red"));
model.addElement(new Product("cyan"));
list.setModel(model);
mainPanel.add(scroll);
}
public static void main(String[] args) throws IOException {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
JFrame frame = new JFrame("WorkerTest");
frame.setContentPane(new Example().mainPanel);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLocation(300, 300);
frame.setMinimumSize(new Dimension(160, 255));
frame.setVisible(true);
}
});
}
class CustomRenderer extends JLabel implements ListCellRenderer<Product> {
private Product product;
public CustomRenderer() {
setOpaque(false);
}
#Override
public Component getListCellRendererComponent(JList<? extends Product> list, Product product, int index, boolean isSelected, boolean cellHasFocus) {
this.product = product;
/**
* in the actual code image is png with alpha channel respectively named to the productID of the JList object
*
* String id = product.getId();
* image = ImageIO.read(getClass().getResource("../../resources/images/" + id + ".png"));
*/
BufferedImage image1 = new BufferedImage(80, 50, BufferedImage.TYPE_INT_RGB);
BufferedImage image2 = new BufferedImage( 80, 75, BufferedImage.TYPE_INT_RGB);
Graphics g = image2.getGraphics();
/**
* this is only an example, in the actual code I might also apply gaussian blurs or rescale several time
*/
g.drawImage(image1,0,0,null);
setIcon(new ImageIcon(image2));
return this;
}
public void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(colorMap.get(product.getColor()));
g.fillRect(0,0,80,75);
}
}
class Product {
String productID;
String color;
public Product(String color) {
this.color = color;
}
public String getColor() {
return color;
}
public String getProductID() {
return productID;
}
}
}
would I have to call a SwingWorker from every getListCellRendererComponent call
to take over the image operations ?
Is SwingWorker even the right tool for this problem?
any help as to how I can make this part of my GUI faster would be greatly appreciated.
EDIT:
Hovercraft Full Of Eels mentioned that preloading the images could help and that loading the images from the renderer is fundamentally wrong.
This leads me to another Question:
I have a list(let's call it list1) with around 3000 objects each object has a 8kb jpg thumbnail which is load via object ID (also during the rendering)
The list displays around 6 to 12 of these thumbnail at the same time (due to the List's Dimension)
when the user selects an object he can press a button to display Card2 from the Cardlayout mentioned in the original question and it's list(list2) with the Object
and all it's related Object in non thumbnail view (500kb png + heavy image operation). Now I think it would be feasible to preload the non thumbnail image of the Object and it's relations selected in the first list which would be around 1-6 images. If I understood correctly what Hovercraft Full Of Eels said, then I could use a SwingWorker to load these Images after the selection of an Object from list1.
But what about the around 3000 images from list1, the program seemingly is not slowed down or becomes unresponsive because they are rather small in size and there are no heavy operations on the thumbnails, but they are still load form the list1's renderer. Would it make sense to preload the several thousand thumbnails ?
btw. feel free to tell me if this kind of question edit is not wished for and if it should be made into a question of itself.
One approach might be the following:
Whenever a cell renderer component for a certain element (Product) is requested, you check whether the matching image is already loaded. If not, you start a Swing worker that does the work of loading and processing the image in the background. When the worker is done, the image is placed into a cache for later lookup. In the meantime, you let the renderer just say "Loading..." or something.
A very quick implementation is here:
And as an MCVE:
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.Collections;
import java.util.Map;
import java.util.Objects;
import java.util.Random;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
import javax.swing.DefaultListModel;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.ListCellRenderer;
import javax.swing.SwingWorker;
public class LazyImageLoadingCellRendererTest
{
private JPanel mainPanel = new JPanel();
private JList<Product> list = new JList<Product>();
private JScrollPane scroll = new JScrollPane();
public LazyImageLoadingCellRendererTest()
{
mainPanel.setBackground(new Color(129, 133, 142));
scroll.setViewportView(list);
scroll.setHorizontalScrollBarPolicy(
JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
scroll.setPreferredSize(new Dimension(80, 200));
list.setCellRenderer(new LazyImageLoadingCellRenderer<Product>(list,
LazyImageLoadingCellRendererTest::loadAndProcessImage));
DefaultListModel<Product> model = new DefaultListModel<Product>();
for (int i=0; i<1000; i++)
{
model.addElement(new Product("id" + i));
}
list.setModel(model);
mainPanel.add(scroll);
}
public static void main(String[] args) throws IOException
{
EventQueue.invokeLater(new Runnable()
{
#Override
public void run()
{
JFrame frame = new JFrame("WorkerTest");
frame.setContentPane(
new LazyImageLoadingCellRendererTest().mainPanel);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLocation(300, 300);
frame.setMinimumSize(new Dimension(160, 255));
frame.setVisible(true);
}
});
}
private static final Random random = new Random(0);
private static BufferedImage loadAndProcessImage(Product product)
{
String id = product.getProductID();
int w = 100;
int h = 20;
BufferedImage image =
new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
Graphics2D g = image.createGraphics();
g.setColor(Color.GREEN);
g.fillRect(0, 0, w, h);
g.setColor(Color.BLACK);
g.drawString(id, 10, 16);
g.dispose();
long delay = 500 + random.nextInt(3000);
try
{
System.out.println("Load time of " + delay + " ms for " + id);
Thread.sleep(delay);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
return image;
}
class Product
{
String productID;
public Product(String productID)
{
this.productID = productID;
}
public String getProductID()
{
return productID;
}
}
}
class LazyImageLoadingCellRenderer<T> extends JLabel
implements ListCellRenderer<T>
{
private final JList<?> owner;
private final Function<? super T, ? extends BufferedImage> imageLookup;
private final Set<T> pendingImages;
private final Map<T, BufferedImage> loadedImages;
public LazyImageLoadingCellRenderer(JList<?> owner,
Function<? super T, ? extends BufferedImage> imageLookup)
{
this.owner = Objects.requireNonNull(
owner, "The owner may not be null");
this.imageLookup = Objects.requireNonNull(imageLookup,
"The imageLookup may not be null");
this.loadedImages = new ConcurrentHashMap<T, BufferedImage>();
this.pendingImages =
Collections.newSetFromMap(new ConcurrentHashMap<T, Boolean>());
setOpaque(false);
}
class ImageLoadingWorker extends SwingWorker<BufferedImage, Void>
{
private final T element;
ImageLoadingWorker(T element)
{
this.element = element;
pendingImages.add(element);
}
#Override
protected BufferedImage doInBackground() throws Exception
{
try
{
BufferedImage image = imageLookup.apply(element);
loadedImages.put(element, image);
pendingImages.remove(element);
return image;
}
catch (Exception e)
{
e.printStackTrace();
return null;
}
}
#Override
protected void done()
{
owner.repaint();
}
}
#Override
public Component getListCellRendererComponent(JList<? extends T> list,
T value, int index, boolean isSelected, boolean cellHasFocus)
{
BufferedImage image = loadedImages.get(value);
if (image == null)
{
if (!pendingImages.contains(value))
{
//System.out.println("Execute for " + value);
ImageLoadingWorker worker = new ImageLoadingWorker(value);
worker.execute();
}
setText("Loading...");
setIcon(null);
}
else
{
setText(null);
setIcon(new ImageIcon(image));
}
return this;
}
}
Note:
This is really just a quick example showing the general approach. Of course, this could be improved in many ways. Although the actual loading process is already pulled out into a Function (thus making it generically applicable for any sort of image, regardless of where it comes from), one major caveat is that: It will try to load all images. A nice extension would be to add some smartness here, and make sure that it only loads the images for which the cells are currently visible. For example, when you have a list of 1000 elements, and want to see the last 10 elements, then you should not have to wait for 990 elements to be loaded. The last elements should be priorized higher and loaded first. However, for this, a slightly larger infrastructure (mainly: an own task queue and some stronger connection to the list and its scroll pane) may be necessary. (I'll possibly tackle this one day, because it might be a nice and interesting thing to have, but until then, the example above might do it...)
would I have to call a SwingWorker from every getListCellRendererComponent call to take over the image operations ?
No, you would in fact never call a background thread from within a key rendering method. In fact this appears to be the main problem with the code above -- you're reading in images from within a rendering method, significantly reducing the perceived responsiveness of your program.
Is SwingWorker even the right tool for this problem?
Perhaps, but not where you're thinking about using it. A SwingWorker will not speed anything up, but by performing long-running tasks in the background, it would avoid blocking the Swing event thread, freezing the GUI. Best would be to read the images, once, perhaps in a SwingWorker if not done during program initiation, and save them to a variable. Do not re-read the image every time you want to render it, if this can be avoided. And again, do not read in the image from within your rendering code as this will significantly reduce the perceived responsiveness of the program.
I was trying to answer a question related to moving a ball across the screen while changing its color over time, however I came through a weird bug, (most probably in my code) and while asking this question I came to a related question but that question is using a Client-Server architecture while mine is simply a Swing app running itself.
What is happening is that when the circle / ball, however you want to name it, reaches the half width of the JPanel or JFrame it becomes invisible or stops.
At first I thought it could be my JPanel being badly positioned, but I added a Border to it, so I could see its dimensions, but it's showing the whole border around the whole space of the JFrame.
Next I thought it could be some arithmetical problem, so I decided to make the ball larger and smaller than what I was originally painting it, giving me the same result, and having the same issue when I enlarge or reduce the window's size.
To get the following output I needed to change the increment by 9 instead of 10 that I was adding originally, because if I change it to 10 it becomes invisible:
The below code produces the above output:
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.geom.Ellipse2D;
import java.util.Random;
import javax.swing.BorderFactory;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
public class ChangingColorBall {
private JFrame frame;
private Timer timer;
private BallPane ballPane;
public static void main(String[] args) {
SwingUtilities.invokeLater(new ChangingColorBall()::createAndShowGui);
}
private void createAndShowGui() {
frame = new JFrame(getClass().getSimpleName());
ballPane = new BallPane();
timer = new Timer(100, e -> {
ballPane.increaseX();
});
ballPane.setBorder(BorderFactory.createLineBorder(Color.RED));
frame.add(ballPane);
frame.pack();
frame.setVisible(true);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
timer.start();
}
#SuppressWarnings("serial")
class BallPane extends JPanel {
private int x;
private static final int Y = 50;
private static final int SIZE = 20;
private Color color;
private Random r;
public void increaseX() {
x += 9;
r = new Random();
color = new Color(r.nextInt(255), r.nextInt(255), r.nextInt(255));
revalidate();
repaint();
}
public int getX() {
return x;
}
public void setX(int x) {
this.x = x;
}
public Color getColor() {
return color;
}
public void setColor(Color color) {
this.color = color;
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
g2d.setColor(color);
// g2d.fill(new Ellipse2D.Double(x, Y, SIZE, SIZE));
g2d.fillOval(x, Y, SIZE, SIZE);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 100);
}
}
}
I also thought it could be something related to the Shapes API, and decided to change it to fillOval as well with the same results, I can't post a GIF yet, but will add it later if necessary.
I'm working under macOS Sierra 10.12.6 (16G29) on a MacBook Pro (13'' Retina display, early 2015) compiling and running it under Java 1.8
I'll test this code later as well on my own PC and not my work's Mac, however, could this be a bug related to Swing's API or a bug in my own code? If so, what am I doing wrong? Since it doesn't seem clear to me
The issue is that you are inadvertently overriding the getX() method defined in JComponent in your BallPane class.
As a result the x coordinate of the the JPanel whenever accessed by getX() is also changing as getX() now returns your field x which is defining how the ball moves and thus resulting in this behavior. You should either remove the method getX() from BallPane or rename it.
I'm trying to write a program that plays musical chords. I'd like to add a window that shows a progress bar displaying the time that the chords play for and how much they have completed. To play the chords, I've been using a slightly modified version of the StdAudio class. So far, I have the following code to be run when I ask a chord to play.
public static void playNotes(double[] frequencies, double duration, double amplitude)
{
PlayAudioGUI g = new PlayAudioGUI(duration);
g.run();
amp = amplitude;
ArrayList<double[]> chord = new ArrayList<double[]>();
for(double freq : frequencies) {
double[] note = StdAudio.tone(freq, duration);
chord.add(note);
}
double[] chordCombined = new double[chord.get(0).length];
for (int i = 0; i < chordCombined.length; i++) {
for (double[] note : chord) {
chordCombined[i] += note[i];
}
chordCombined[i] /= chord.size();
}
StdAudio.play(chordCombined);
}
I've never attempted multithreading before, so I don't know what I'm doing wrong. When I run the code, It shows an empty window while it plays the chord, then afterwards displays the window properly. I'd like for it to display the window at the same time as playing the audio.
Here is my code for the window's class.
import java.awt.Container;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JProgressBar;
import javax.swing.Timer;
public class PlayAudioGUI implements Runnable {
private JFrame window;
private JProgressBar prog;
private double duration;
private Timer t;
class TimerListener implements ActionListener {
// This runs every few milliseconds, depending on the delay set below
public void actionPerformed(ActionEvent event) {
prog.setValue(prog.getValue() + 1);
// Stop the timer and hide the window when the progress bar
// completes
if (prog.getValue() == prog.getMaximum()) {
t.stop();
window.setVisible(false);
}
}
}
public PlayAudioGUI(double duration) {
this.window = new JFrame("Playing audio...");
this.duration = duration;
}
#Override
public void run() {
// Setting up gridbag layout. I will add more components later.
Container pane = this.window.getContentPane();
pane.setLayout(new GridBagLayout());
GridBagConstraints c = new GridBagConstraints();
c.gridx = 0;
c.gridy = 0;
c.insets = new Insets(30, 30, 30, 30);
// Display the approximate duration
String clippedDuration;
if (Double.toString(duration).length() > 5) {
clippedDuration = Double.toString(duration).substring(0, 4);
} else {
clippedDuration = Double.toString(duration);
}
String message = "Playing audio for " + clippedDuration + " seconds";
pane.add(new JLabel(message), c);
// Make a progressbar
c.gridy = 1;
this.prog = new JProgressBar();
this.prog.setMinimum(0);
this.prog.setMaximum(250);
pane.add(this.prog, c);
// More window management stuff
this.window.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
this.window.pack();
this.window.setVisible(true);
// Set up the timer
ActionListener listener = new TimerListener();
final int DELAY = (int) (4 * this.duration); // This works, I did the
// math :)
t = new Timer(DELAY, listener);
t.start();
}
}
Thanks for your help.
Suggestions:
The new dependent dialog window should be just that, a dialog such as a JDialog, not a new JFrame which is creating a whole separate application.
You know that you should be doing your sound creation in a background thread, and your blank screen is being caused by just this problem, and yet I see no thread creation in your code -- why?
Myself, I'd not use a Swing Timer, and poll data, but rather do all within a SwingWorker, within the SwingWorker's doInBackground method I'd update its progress state, and I'd add a PropertyChangeListener to the SwingWorker and monitor this state.
As an aside, you will almost never want to create a Runnable class and then call its run() method. If you're creating the Runnable to allow it to run in a background thread, then you'd likely place it into a Thread and then call start() on the Thread. Since your code above should run on the Swing event thread, then if it is not being called from this thread, it should be queued on to it via SwingUtilities.invokeLater(myRunnable);
I'm not sure how you can get a progress value from your StdAudio library. If there's a way, then use it to set the SwingWorker's progress state via its setProgress(...) method. If not, then you could guess, I suppose or you may be better off using an indeterminate progress bar. I believe JProgressBar has a method called setIndeterminate(true) that would work for this.
I've been taking AP Computer Science this year as a sophomore in high school and we mainly cover material like loops, classes, methods, general CS logic, and some math stuff. I am missing what I really loved about coding in the first place, making games. Now every game I have made had some sort of way to manage it whether it was using timers in visual basic or a XNA plugin for c# that setup a update method for me. The problem is I have not learned how to do this for java in my course. I've read up a little on threads and implements runnable but i'm not really sure where I'm going with it.
Class 1
import java.awt.FlowLayout;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JLabel;
public class GFXScreen extends JFrame
{
/**
* #param screenHeigth
* #param screenHeigth
* #param The file name of the image. Make sure to include the extension type also
* #param The title at the top of the running screen
* #param The height of the screen
* #param The width of the screen
*/
public GFXScreen(String fileName, String screenTitle, int screenHeight, int screenWidth)
{
setLayout(new FlowLayout());
image1 = new ImageIcon(getClass().getResource(fileName));
label1 = new JLabel(image1);
this.add(label1);
//Set up JFrame
this.setDefaultCloseOperation(EXIT_ON_CLOSE);
this.setVisible(true);
this.setTitle(screenTitle);
this.setSize(screenWidth, screenHeight);
}
/**
* #param desired amount to move picture
*/
public void updatePic(int increment)
{
//update pos
label1.setBounds(label1.bounds().x, label1.bounds().y - increment,
label1.bounds().width, label1.bounds().height);
}
private ImageIcon image1;
private JLabel label1;
}
Class 2
public class MainClass implements Runnable {
public static void main(String[] args)
{
(new Thread(new MainClass())).start();
GFXScreen gfx = new GFXScreen("pixel_man.png", "pixel_Man", 1000, 1000);
}
public void run()
{
gfx.updatePic(1);
}
}
In this instance what I want to happen is, I want a picture that starts in the top to slowly move down smoothly to the bottom. How would i do this?
Suggestions:
Again, a Swing Timer works well for simple Swing animations or simple game loops. It may not be the greatest choice for complex or rigorous tame loops as its timing is not precise.
Most game loops will not be absolutely precise with time slices
And so your game model should take this into consideration and should note absolute time slices and use that information in its physics engine or animation.
If you must use background threading, do take care that most all Swing calls are made on the Swing event thread. To do otherwise will invite pernicious infrequent and difficult to debug program-ending exceptions. For more details on this, please read Concurrency in Swing.
I avoid using null layouts, except when animating components, as this will allow my animation engine to place the component absolutely.
When posting code here for us to test, it's best to avoid code that uses local images. Either have the code use an image easily available to all as a URL or create your own image in your code (see below for a simple example).
Your compiler should be complaining to you about your using deprecated methods, such as bounds(...), and more importantly, you should heed those complaints as they're there for a reason and suggest increased risk and danger if you use them. So don't use those methods, but instead check the Java API for better substitutes.
Just my own personal pet peeve -- please indicate that you've at least read our comments. No one likes putting effort and consideration into trying to help, only to be ignored. I almost didn't post this answer because of this.
For example:
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import javax.swing.AbstractAction;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
#SuppressWarnings("serial")
public class GfxPanel extends JPanel {
private static final int BI_WIDTH = 26;
private static final int BI_HEIGHT = BI_WIDTH;
private static final int GAP = 6;
private static final Point INITIAL_LOCATION = new Point(0, 0);
private static final int TIMER_DELAY = 40;
public static final int STEP = 1;
private ImageIcon image1;
private JLabel label1;
private Point labelLocation = INITIAL_LOCATION;
private int prefW;
private int prefH;
private Timer timer;
public GfxPanel(int width, int height) {
// the only time I use null layouts is for component animation.
setLayout(null);
this.prefW = width;
this.prefH = height;
// My program creates its image so you can run it without an image file
image1 = new ImageIcon(createMyImage());
label1 = new JLabel(image1);
label1.setSize(label1.getPreferredSize());
label1.setLocation(labelLocation);
this.add(label1);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(prefW, prefH);
}
public void startAnimation() {
if (timer != null && timer.isRunning()) {
timer.stop();
}
labelLocation = INITIAL_LOCATION;
timer = new Timer(TIMER_DELAY, new TimerListener());
timer.start();
}
// My program creates its image so you can run it without an image file
private Image createMyImage() {
BufferedImage bi = new BufferedImage(BI_WIDTH, BI_HEIGHT,
BufferedImage.TYPE_INT_ARGB);
Graphics2D g2 = bi.createGraphics();
g2.setColor(Color.red);
g2.fillRect(0, 0, BI_WIDTH, BI_HEIGHT);
g2.setColor(Color.blue);
int x = GAP;
int y = x;
int width = BI_WIDTH - 2 * GAP;
int height = BI_HEIGHT - 2 * GAP;
g2.fillRect(x, y, width, height);
g2.dispose();
return bi;
}
private class TimerListener implements ActionListener {
#Override
public void actionPerformed(ActionEvent e) {
int x = labelLocation.x + STEP;
int y = labelLocation.y + STEP;
labelLocation = new Point(x, y);
label1.setLocation(labelLocation);
repaint();
if (x + BI_WIDTH > getWidth() || y + BI_HEIGHT > getHeight()) {
System.out.println("Stopping Timer");
((Timer) e.getSource()).stop();
}
}
}
private static void createAndShowGui() {
final GfxPanel gfxPanel = new GfxPanel(900, 750);
JButton button = new JButton(new AbstractAction("Animate") {
#Override
public void actionPerformed(ActionEvent arg0) {
gfxPanel.startAnimation();
}
});
JPanel buttonPanel = new JPanel();
buttonPanel.add(button);
JFrame frame = new JFrame("GFXScreen");
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
frame.getContentPane().add(gfxPanel);
frame.getContentPane().add(buttonPanel, BorderLayout.PAGE_END);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGui();
}
});
}
}
What I always use is an infinite loop that calls an update method each iteration, in that method, you would do whatever was required to update the state of the game or render a GUI.
Example
public static void main(String[] args){
// Initialise game
while(true){
updateGame();
}
}
public static void updateGame(){
// Update things here.
}
What I also do ,which is a little more complex, is create and interface called IUpdateListener and have certain classes that are specialised for a certain element of the game. I would example have an InputListener, an AIListener, each handling a certain element of game updating.
public interface IUpdateListener{
public void update();
}
public class Main{
public static ArrayList<IUpdateListener> listeners = new ArrayList<IUpdateListener>();
public static void main(String[] args){
listeners.add(new InputListener());
while(true){
for(IUpdateListener listener : listeners){
listener.update();
}
}
}
}
public class InputListener implements IUpdateListener{
public void update(){
// Handle user input here etc
}
}
I'm working on a school project and I have to create a person's face in java that the user of my app can edit. Each facial characteristic is supposed to have a couple of options, so the first one I tried doing is the eye. However, I'm having trouble when I try to interact with the user in the eyeComponent class.
It prints the users color options (1-4) in the JVM, and opens a blank JFrame window, but in the JVM, it doesn't allow for a user's response. After it prints out the color options, the program just ends, and I'm not sure why it's not allowing the user to respond. I posted the code for both the EyeComponent and PersonViewer classes below.
EyeComponent
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Line2D;
import javax.swing.JComponent;
import java.util.Scanner;
public class EyeComponent extends JComponent
{
public void paintComponent(Graphics g)
{
Scanner in = new Scanner(System.in);
Graphics2D g2 = (Graphics2D) g;
Ellipse2D.Double head = new Ellipse2D.Double(5,20,100,150);
System.out.println("What color would you like the eyes to be?");
System.out.println("Select \n1:blue \n2:red \n3:yellow \n4:green");
int response = in.nextInt();
if (response == 1)
{ g2.setColor(Color.BLUE);}
else if (response == 2)
{ g2.setColor(Color.RED);}
else if (response == 3)
{ g2.setColor(Color.YELLOW);}
else if (response == 4)
{ g2.setColor(Color.GREEN);}
g2.draw(head);
}
}
PersonViewer
import javax.swing.*;
import java.util.Scanner;
public class personViewer //creates class called engine of scope public
{
public static void main (String [] args) //main method for engine class
{
JFrame frame = new JFrame();
frame.setSize(150, 250);
frame.setTitle("Face");
EyeComponent component = new EyeComponent();
frame.add(component);
frame.setVisible(true);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
}
Never use a Scanner or any blocking code inside of paintComponent. Ever. In fact this blocking code should never be called on the Swing event thread, but especially so in paintComponent as it will grind your program's graphics to a halt. Just don't do it.
This is (another) Event Dispatching Thread issue.
You should NEVER request input from the user via the console from within a GUI program, apart from breaking the whole point of GUI, you have know experienced what happens when you block the main event/repaint thread.
Swing (like all GUI's) are event driven environments. The user clicks or types something and the program responds to it. In this form, they are unlike console programs, which tend to be more linear in nature.
Swing uses a single thread to perform all it's painting and event notification from. Doing anything that stops this thread from running will cause you application to appear as if it has hung and become non-responsive.
Start by having a read of Creating GUIs with Swing and then have a read of Performing Custom Painting and finally I would highly recommend you take a look at Painting in AWT and Swing
Simple Example
Do not have your scanner in a method which you do not have control about.
I refactored you code a bit to more OOP approach.
import javax.swing.*;
import java.awt.Color;
import java.util.Scanner;
public class PersonViewer // creates class called engine of scope public
{
public static void main(String[] args) // main method for engine class
{
Configurator conf = Configurator.getInstance();
Scanner in = new Scanner(System.in);
System.out.println("What color would you like the eyes to be?");
System.out.println("Select \n1:blue \n2:red \n3:yellow \n4:green");
int response = in.nextInt();
switch (response) {
case 1:
conf.setEyeColor(Color.BLUE);
break;
case 2:
conf.setEyeColor(Color.RED);
break;
case 3:
conf.setEyeColor(Color.YELLOW);
break;
case 4:
conf.setEyeColor(Color.GREEN);
break;
}
JFrame frame = new JFrame();
frame.setSize(150, 250);
frame.setTitle("Face");
EyeComponent component = new EyeComponent();
frame.add(component);
frame.setVisible(true);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
}
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.geom.Ellipse2D;
import javax.swing.JComponent;
public class EyeComponent extends JComponent {
Configurator conf = Configurator.getInstance();
public void paintComponent(Graphics g) {
Graphics2D g2 = (Graphics2D) g;
Ellipse2D.Double head = new Ellipse2D.Double(5, 20, 100, 150);
g2.setColor(conf.getEyeColor());
System.out.println(conf.getEyeColor());
g2.draw(head);
}
}
import java.awt.Color;
public class Configurator {
private static Configurator instance = null;
public static Configurator getInstance() {
if (instance==null) {
instance = new Configurator();
System.out.println("first called");
}
else {
System.out.println("next calls");
}
return instance;
}
private Color eyeColor;
public Color getEyeColor() {
return eyeColor;
}
public void setEyeColor(Color eyeColor) {
this.eyeColor = eyeColor;
}
}
Hope this help to get a point. And it works.