Welcome to the Java Programming Forums


The professional, friendly Java community. 21,500 members and growing!


The Java Programming Forums are a community of Java programmers from all around the World. Our members have a wide range of skills and they all have one thing in common: A passion to learn and code Java. We invite beginner Java programmers right through to Java professionals to post here and share your knowledge. Become a part of the community, help others, expand your knowledge of Java and enjoy talking with like minded people. Registration is quick and best of all free. We look forward to meeting you.


>> REGISTER NOW TO START POSTING


Members have full access to the forums. Advertisements are removed for registered users.

Results 1 to 7 of 7

Thread: Repaint doesn't repaint?

  1. #1
    Junior Member
    Join Date
    Jan 2010
    Posts
    4
    Thanks
    2
    Thanked 0 Times in 0 Posts

    Default Repaint doesn't repaint?

    Hi all. I have written a card game using Swing and wanted to use a few small animations here and there to clarify what is going on in a few cases where cards are hidden or revealed. We're talking very simple animations, nothing more than some cards sliding together into one stack or sliding back apart.

    The problem is, I can't seem to get any sort of animation working because my GUI isn't repainted until the entirety of the animation is finished. That is, if I animate a card sliding to the right by calling setBounds with increasing X coordinates, all that I SEE happening is that the card suddenly jumps to the right after all of the setBounds calls have finished.

    As follows is a simplified mockup that should replicate the structure of my game. The container for a single object may seem unnecessary but there is more going on in the real thing. Same goes for the AnimatedThing class that's little more than a JLabel.

    This is my first Swing GUI ever, so if I'm doing something that doesn't make sense, it's because I've cobbled together several examples to get the thing up and running. Please do let me know if there is a more sensible way to do any of this.

    The main class...
    public class AnimationExample
    {
    	public AnimationExample()
    	{}
     
    	public static void main(String[] args)
    	{
    		final AnimationExample example = new AnimationExample();
    		javax.swing.SwingUtilities.invokeLater(new Runnable() 
    		{public void run(){AnimationViewer.disp();}});
    	}
    }

    A viewer class that sets up a pane and listens for a mouse click before starting the animation...
    import javax.swing.*;
    import java.awt.*;
    import java.awt.event.*;
     
    public class AnimationViewer extends JPanel implements MouseListener
    {
    	public JLayeredPane pane;
    	private AnimatedThingsContainer animatedThingsContainer;
     
    	public AnimationViewer()
    	{
    		setLayout(new BoxLayout(this, BoxLayout.PAGE_AXIS));
    		pane = new JLayeredPane();
    		pane.setPreferredSize(new Dimension(200, 200));
    		add(pane);
    		pane.addMouseListener(this);
     
    		AnimatedThing thing = new AnimatedThing();
    		pane.add(thing);
    		animatedThingsContainer = new AnimatedThingsContainer(thing);
    	}
     
    	public static void disp()
    	{
    		//Create and set up the window.
    		JFrame frame = new JFrame("Test");
    		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
     
    		//Create and set up the content pane.
    		AnimationViewer view = new AnimationViewer();
     
    		JScrollPane scrollPane = new JScrollPane(view.pane);
    		frame.setContentPane(scrollPane);
     
    		//Display the window.
    		frame.pack();
    		frame.setVisible(true);
    	}
     
    	public void mouseClicked(MouseEvent e)
    	{
    		System.out.println("animating...");
    		animatedThingsContainer.animate();
    	}
     
    	public void mouseExited(MouseEvent e){}
    	public void mouseEntered(MouseEvent e){}
    	public void mouseReleased(MouseEvent e){}
    	public void mousePressed(MouseEvent e){}
     
     
    }

    A container for an AnimatedThing that handles the logic of where the AnimatedThing should go.
    public class AnimatedThingsContainer
    {
    	private AnimatedThing animatedThing;
     
    	public AnimatedThingsContainer(AnimatedThing thing)
    	{
    		animatedThing = thing;
    	}
     
    	public void animate()
    	{
    		int currentX = 0;
    		int currentY = 0;
    		for(int i = 0; i < 50; ++i)
    		{
    			System.out.println("moving one pixel...");
     
    			animatedThing.setBounds(currentX, currentY, AnimatedThing.width, AnimatedThing.height);
    			currentX++;
     
    			animatedThing.validate();
    			animatedThing.repaint();
    			try{Thread.sleep(50);}catch(Exception e){System.out.println("oh noes");}
    		}
    		System.out.println("done.");
    	}
    }

    Finally, the AnimatedThing itself.
    import javax.swing.*;
     
    public class AnimatedThing extends JLabel
    {	
    	public static int width = 50;
    	public static int height = 50;
     
    	public AnimatedThing()
    	{
    		setIcon(createImageIcon("test.gif"));
    		setVerticalAlignment(JLabel.BOTTOM);
    		setOpaque(false);
    		setBounds(0, 0, width, height);
    	}
     
    	/* createImaceIcon() method taken from The Java Tutorials: How to Use Icons.*/	
    	public static ImageIcon createImageIcon(String path) 
    	{
    		java.net.URL imgURL = AnimatedThing.class.getResource(path);
    		if (imgURL != null) 
    		{
    			return new ImageIcon(imgURL);
    		} 
    		else 
    		{
    			System.err.println("Couldn't find file: " + path);
    			return null;
    		}
    	}
    }

    As you can see, I am calling validate and repaint after moving the AnimatedThing, and I am sleeping to give the user time to see the animation and hopefully to give the drawing thread time to repaint, but nothing seems to work. All I see when I click is the AnimatedThing jumping suddenly to the right after all of the movements are done.


    Thanks for any help you can offer!

    Edit: I am using Java version 1.6, if that is relevant.
    Last edited by PotataChipz; January 16th, 2010 at 09:28 PM. Reason: Added java version.


  2. #2
    Administrator copeg's Avatar
    Join Date
    Oct 2009
    Location
    US
    Posts
    5,264
    Thanks
    176
    Thanked 821 Times in 764 Posts
    Blog Entries
    5

    Default Re: Repaint doesn't repaint?

    Your animate function is being performed on the event dispatch thread (EDT), and since all drawing/events/etc... is queued onto this thread, no calls will be performed until your function exits (imagine the EDT as the worker which does everything having to do with Swing: repaints, events, etc...but it can only do one thing at a time).

    So to animate, you must do so from a separate thread or SwingTimer, which works in parallel to the EDT. For something like this I'd recommend a SwingTimer, as it fires off events at intervals which is great for animation. If you choose to use a Thread, be sure that all calls to Swing changes (eg repaint) be forced onto the EDT using Swing Utilities.

  3. The Following User Says Thank You to copeg For This Useful Post:

    PotataChipz (January 18th, 2010)

  4. #3
    Junior Member
    Join Date
    Jan 2010
    Posts
    4
    Thanks
    2
    Thanked 0 Times in 0 Posts

    Default Re: Repaint doesn't repaint?

    Copeg, thanks a bunch! I can get the animation working using a swing timer but now I can't seem to understand how to wait for the timer to finish before doing something else to the GUI. I have updated my files to include a reset() method that resets the position of an AnimatedThing after animating it moving to the right. If I call animate() and then reset(), of course the reset happens during the animation. But how do I wait for the timer to finish before calling reset? Sleeping just causes a delay before the animation happens. The timer itself seems to be on the same thread as everything else, so any sleeping or busy waiting I try to do just seems to prevent the timer from firing events. Am I correct? What am I missing?

    The main class...
    public class AnimationExample
    {
    	public AnimationExample()
    	{}
     
    	public static void main(String[] args)
    	{
    		final AnimationExample example = new AnimationExample();
    		javax.swing.SwingUtilities.invokeLater(new Runnable() 
    		{public void run(){AnimationViewer.disp();}});
    	}
    }

    The viewer class which waits for a mouse click
    import javax.swing.*;
    import java.awt.*;
    import java.awt.event.*;
     
    public class AnimationViewer extends JPanel implements MouseListener
    {
    	public JLayeredPane pane;
    	private AnimatedThingsContainer animatedThingsContainer;
     
    	public AnimationViewer()
    	{
    		setLayout(new BoxLayout(this, BoxLayout.PAGE_AXIS));
    		pane = new JLayeredPane();
    		pane.setPreferredSize(new Dimension(200, 200));
    		add(pane);
    		pane.addMouseListener(this);
     
    		AnimatedThing thing = new AnimatedThing();
    		pane.add(thing);
    		animatedThingsContainer = new AnimatedThingsContainer(thing);
    	}
     
    	public static void disp()
    	{
    		//Create and set up the window.
    		JFrame frame = new JFrame("Test");
    		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
     
    		//Create and set up the content pane.
    		AnimationViewer view = new AnimationViewer();
     
    		JScrollPane scrollPane = new JScrollPane(view.pane);
    		frame.setContentPane(scrollPane);
     
    		//Display the window.
    		frame.pack();
    		frame.setVisible(true);
    	}
     
    	public void mouseClicked(MouseEvent e)
    	{
    		System.out.println("animating...");
    		animatedThingsContainer.animate();
    		animatedThingsContainer.reset();
    	}
     
    	public void mouseExited(MouseEvent e){}
    	public void mouseEntered(MouseEvent e){}
    	public void mouseReleased(MouseEvent e){}
    	public void mousePressed(MouseEvent e){}
     
     
    }

    The AnimatedThing container
    import java.awt.event.*;
     
    public class AnimatedThingsContainer
    {
    	private AnimatedThing animatedThing;
    	private int x;
    	private int y;
    	private int currentX;
    	private int currentY;
    	private int count;
    	private javax.swing.Timer t;
     
    	public AnimatedThingsContainer(AnimatedThing thing)
    	{
    		animatedThing = thing;
    		x = 0;
    		y = 0;
    		currentX = x;
    		currentY = y;
    		count = 0;
    	}
     
    	public void animate()
    	{
    		t = new javax.swing.Timer(10, new ActionListener() {
              public void actionPerformed(ActionEvent e) {
                  animateOneFrame();
              }
           });
    		 t.start();
     
    	}
     
    	private void animateOneFrame()
    	{
    		count++;
    		if(count > 50)
    		{
    			t.stop();
    		}
    		System.out.println("moving one pixel...");
     
    		animatedThing.setBounds(currentX, currentY, AnimatedThing.width, AnimatedThing.height);
    		currentX++;
     
    		animatedThing.validate();
    		animatedThing.repaint();
    		try{Thread.sleep(50);}catch(Exception e){System.out.println("oh noes");}
    	}
     
    	public void reset()
    	{
    		System.out.println("resetting.");
    		count = 0;
    		animatedThing.setBounds(x, y, AnimatedThing.width, AnimatedThing.height);
    	}
    }

    The AnimatedThing.
    import javax.swing.*;
     
    public class AnimatedThing extends JLabel
    {	
    	public static int width = 50;
    	public static int height = 50;
     
    	public AnimatedThing()
    	{
    		setIcon(createImageIcon("test.gif"));
    		setVerticalAlignment(JLabel.BOTTOM);
    		setOpaque(false);
    		setBounds(0, 0, width, height);
    	}
     
    	/* createImaceIcon() method taken from The Java Tutorials: How to Use Icons.*/	
    	public static ImageIcon createImageIcon(String path) 
    	{
    		java.net.URL imgURL = AnimatedThing.class.getResource(path);
    		if (imgURL != null) 
    		{
    			return new ImageIcon(imgURL);
    		} 
    		else 
    		{
    			System.err.println("Couldn't find file: " + path);
    			return null;
    		}
    	}
    }

    Thanks in advance!

  5. #4
    Administrator copeg's Avatar
    Join Date
    Oct 2009
    Location
    US
    Posts
    5,264
    Thanks
    176
    Thanked 821 Times in 764 Posts
    Blog Entries
    5

    Default Re: Repaint doesn't repaint?

    Just call it when you stop your timer:

    		count++;
    		if(count > 50)
    		{
    			t.stop();
     			[COLOR="Red"]reset();[/COLOR]
    		}

  6. #5
    Junior Member
    Join Date
    Jan 2010
    Posts
    4
    Thanks
    2
    Thanked 0 Times in 0 Posts

    Default Re: Repaint doesn't repaint?

    Right, but that's going to make the larger application pretty messy because I might have some loose ends to tie up in the Viewer after calling reset, and then I'll have to rely on my animation calling back to some function in the Viewer. I want to have multiple types of animations so it's not going to be as simple as always calling the same function to finish things up, either. Is there any way I can wait on the timer from within the AnimationViewer? I just tried putting an animationDone flag in the view and having the container call a finish() method that would set the flag to true. But waiting while(!animationDone) prevents the timer from ever firing events, I assume because it's in the same thread and it's waiting for that loop to finish before it can do anything. Of course I can create a finish() method that calls reset(), but that just goes back to my initial objection: in the real code I'm going to have to create a bunch of startAnimationA(), startAnimationB(), finishAnimationA(), finishAnimationB() methods for each type of animation I want, since I don't always wrap up in the same way.

    Should I just have the Swing Timer and all of the animation happen in a separate thread and sleep the main thread until the Timer is done? (I still don't quite understand what in the Timer happens in a separate thread; is this redundant somehow?)

    Thanks again!

  7. #6
    Administrator copeg's Avatar
    Join Date
    Oct 2009
    Location
    US
    Posts
    5,264
    Thanks
    176
    Thanked 821 Times in 764 Posts
    Blog Entries
    5

    Default Re: Repaint doesn't repaint?

    I'm not fully sure I understand what you're needs are, but assuming I do there are probably dozens of ways to accomplish it...its just how messy/clean/re-useable you want the code. One suggestion would be to set up a listener type system: you register a listener (usually an interface) with an object (stored in some way like an arraylist), and those listeners are fired at the right time. In this case you could just create a new Thread rather than using the Timer. If so you can call sleep in a loop, redrawing for each loop and when the loop reaches the max time exit the loop and fire all your listeners (make sure to make all calls to swing on the EDT). There are also more complex ways that I won't go into but the just is you extend the Runnable interface in such a way as to allow one to know when a thread has finished. Alternatively you could extend Timer, adding the listener code and in your case overriding stop() to run the listeners and then call super.stop().

  8. The Following User Says Thank You to copeg For This Useful Post:

    PotataChipz (January 18th, 2010)

  9. #7
    Junior Member
    Join Date
    Jan 2010
    Posts
    4
    Thanks
    2
    Thanked 0 Times in 0 Posts

    Default Re: Repaint doesn't repaint?

    After some playing I think I understand my problem a little better. What I want is to start a specific animation in response to a mouse click, wait for the animation to finish, then perform some more wrap-up tasks specific to THAT animation. My frustration comes from the fact that mouse events happen on the EDT, so there is no way for me to wait() for the animation to finish because the current thread will always be the EDT, and suspending the current thread will always suspend the drawing as well.

    So for instance, if the user presses a "compress pile of cards" button, I want the View to start the animation of cards sliding together, wait for the animation to finish, then update the Model to reflect that that pile is now compressed.

    Something like...
    class View
    {
    	Model model;
    	Animator animator;
     
    	void compressCards()
    	{
    		animator.compressCards(); //start the animation of cards being compressed
    		animator.wait(); //suspend the current thread until animation is finished
    		disableCompressCardsButton(); //the cards are compressed, remove the button responsible for compressing them
    		model.compressCards(); //update the model
    	}
    }

    I want this all to happen in the space of one function so that I don't have a bunch of startAnimation() and finishAnimation() functions in the View for each unique type of action/animation. (I have about five more planned.) Is there any way to avoid this? I'm avoiding the suggestion of a listener because it just seems messy to me to add so many finishAnimation() functions.

    Do I just want the impossible?

Similar Threads

  1. Replies: 1
    Last Post: October 23rd, 2009, 03:17 PM