Double Buffering with a JPanel


*** UPDATE *** I have changed the topic slightly from my first post. Start from this post for the updated question:


Hi all,

I’m trying to get two JLabels to update at exactly the same time which I would like to do from my repaint method in my loop. However, the problem is that calling setText and setBackground on the JLables automatically calls the repaint method, therefore these labels aren’t actually getting updated by my repaint method in my loop.

I’m trying to implement a method of double buffering where I update the labels off screen and then in my repaint method I make an Image of the JPanel that holds the JLabels and display that Image. However, I cannot get this to work. The JFrame shows up solid black.

I believe that my problem is getting the Image of the JPanel and I’ve tried a couple different methods, but neither of them worked. I left both methods in my code so you can see the two methods I tried and one of them is commented out.

I did check to make sure everything else in my loop and double buffer were working correctly by removing all of the JPanel Image creating stuff in my paintComponent method and added a line to draw an oval with Graphics…
g.fillOval(x, 200, 20, 20);
Then I incremented “x” in my update method and the oval does move successfully across the screen. So everything else appears to be working.

Any help in getting my JPanel double buffer to work would be greatly appreciated…of if there is a better way I could be going about this, I’m open to suggestions there as well. Thanks! :slight_smile:

Here’s my sample code:

TestApplication.java

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.event.*;

public class TestApplication {
	static TestApplication app;
	static Loop loop;
	static Canvas canvas;
	
	JPanel			mainPanel;
	JLabel[]			beatDisplay;
	
	boolean isPlaying=true;
	
	int[] count = { 1, 1 };
	boolean[] isGreen = { true, true };
	
	JPanel center = new JPanel();
	
	public static void main (String[] args) {		
		app = new TestApplication();
		
		SwingUtilities.invokeLater(new Runnable(){
			public void run() {
				app.gui_Create();
				app.loop();
			}
		});
	}
	
	public void gui_Create() {
		gui_MainPanel();
		gui_AddContent();
		canvas = new Canvas(app);
	}
	public void gui_MainPanel() {
		mainPanel = new JPanel();
		mainPanel.setPreferredSize(new Dimension(400,400));
		mainPanel.setLayout(new BorderLayout());
		mainPanel.setVisible(true);
	}
	public void gui_AddContent() {
		center.setSize(new Dimension(400,400));
		center.setMaximumSize(new Dimension(400,400));
		center.setMinimumSize(new Dimension(400,400));
		center.setPreferredSize(new Dimension(400,400));
		center.setOpaque(true);
		center.setVisible(true);
		
		beatDisplay = new JLabel[2];
		for (int i=0; i<2; i++){
			String buttonName = "~ SOUND " + i + " ~";
			beatDisplay[i] = new JLabel(buttonName);
			beatDisplay[i].setPreferredSize(new Dimension(400, 50));
			beatDisplay[i].setHorizontalAlignment(SwingConstants.CENTER);
			beatDisplay[i].setOpaque(true);
			beatDisplay[i].setFont(new Font("Serif", Font.BOLD, 40));
			
			center.add(beatDisplay[i]);
		}
		
//	 	canvas.add(center, BorderLayout.CENTER);
	}
	
	public void loop(){
		loop = new Loop(app, canvas);
		loop.start();
	}
	
	public void update(){
		if (loop.frameCount%30 == 0){
			for (int i=0; i<2; i++){
				beatDisplay[i].setText(""+count[i]);
				if (isGreen[i]){
					beatDisplay[i].setBackground(Color.GREEN);
					isGreen[i] = false;
				}
				else{
					beatDisplay[i].setBackground(Color.YELLOW);
					isGreen[i] = true;
				}
				
				count[i]++;
				if (count[i] > 4)
					count[i] = 1;
			}
		}
	}
}

Loop.java

public class Loop extends Thread {
	static TestApplication app;
	static Canvas canvas;
	
	int fps = 60;
	long frameCount=1;
	
	public Loop(TestApplication appIn, Canvas canvasIn){
		app = appIn;
		canvas = canvasIn;
	}
	
	public void run(){
		while (app.isPlaying){
			app.update();
			canvas.repaint();
			try { Thread.sleep(fps); } catch (InterruptedException ie){}
			frameCount++;
		}
	}
}

Canvas.java

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.event.*;

import java.awt.image.BufferedImage;

public class Canvas extends JFrame{
	static TestApplication app;
	
	Image dbImage;
	Graphics dbg;
	
	public Canvas(TestApplication appIn){
		app = appIn;
		
		setTitle("Sound Test");
		setPreferredSize(new Dimension(400,400));
		setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		pack();
		setVisible(true);
	}
	
	public void setMainPanel(JPanel panel){
		setContentPane(panel);
	}
	
	public void paint(Graphics g){
		dbImage = createImage(getWidth(), getHeight());
		dbg = dbImage.getGraphics();
		paintComponent(dbg);
		g.drawImage(dbImage, 0, 0, this);
	}
	
	public void paintComponent(Graphics g){
		Image temp;
		
		// NEITHER OF THESE WORK FOR CRREATING THE "TEMP" IMAGE
//	 	temp = app.center.createImage(app.center.getWidth(), app.center.getHeight());	
 		temp = createImage(app.center);
		
 		g.drawImage(temp, 0, 0, this);
		
		repaint();
	}
	
	private static BufferedImage createImage(JPanel panel) {
		int w = panel.getWidth();
		int h = panel.getHeight();
		BufferedImage bi = new BufferedImage(400, 400, BufferedImage.TYPE_INT_RGB);
		Graphics2D g = bi.createGraphics();
		return bi;
	}
}

If the labels calling additional repaints() is actually your problem, why not this:


loop {
    canvas.setIgnoreRepaint(true);
    app.update();
    canvas.setIgnoreRepaint(false);
    canvas.repaint();
    sleep();
}

Why would you want to force the JLabel’s to repaint at the same time?
It’s just JLabels, so there shouldn’t be any performance issues with letting Swing repaint them automatically.

The method setIgnoreRepaint(true) tells Swing/AWT to ignore the repaint-messages from the operating-system, but does not disable the repaint()-calls that are made in setText(). See here: http://docs.oracle.com/javase/7/docs/api/java/awt/Component.html#setIgnoreRepaint(boolean)

This whole approach doesn’t make a lot of sense to me.

Swing is already double-buffered. Why do you think you need to handle double-buffering yourself?

Why are you updating a JLabel “off-screen”? All changes to Swing components must be done on the EDT anyway, so I’m not sure what kind of performance gain you’re expecting from this.

In any case, when do you draw the JPanel to the BufferedImage?

Indeed, you’re quite right. Forgot about that distinction.

@OP I think everyone would appreciate an sscce of what it is you’re actually trying to do.

Wow…you guys are quick! :smiley: Thanks everyone!

To everyone: Basically what I’m trying to do is prevent one of the label displays from getting updated slightly before the other one. This is for a metronome application and sometimes (especially at faster speeds) one display will change just slightly before the other one when they should both change at precisely the same time. That’s basically what I’m trying to prevent from happening with my double buffering method. I know double buffering is usually used to prevent “flickering”, but I figured that approach might work for what I need also.

KevinWorkman, I’ve read up on painting but I still don’t fully understand some of the “magic” that happens behind the painting methods. I found an example of double buffering using Graphics that moved an oval around the screen with the arrow keys and I tried replacing the “draw oval” part with a “draw Image” which I’m trying to get from the JPanel. If drawing an oval works (and it does when I add that into my example code), then drawing an image should theoretically work as well as long as I get the get the Image correctly from the JPanel…which unfortunately isn’t working.

I don’t see any de-syncing here:

public static void main(String[] a) throws InterruptedException {
	JFrame frame = new JFrame();
	frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
	frame.setSize(400, 400);
	frame.setLocationRelativeTo(null);
	
	JPanel panel = new JPanel(); // JPanel is double buffered by default
	frame.add(panel);
	panel.setLayout(new GridBagLayout());
	
	JLabel label1 = new JLabel("Label 1");
	JLabel label2 = new JLabel("Label 2");
	JLabel label3 = new JLabel("Label 3");
	JLabel label4 = new JLabel("Label 4");
	label1.setOpaque(true);
	label2.setOpaque(true);
	label3.setOpaque(true);
	label4.setOpaque(true);
	panel.add(label1);
	panel.add(label2);
	panel.add(label3);
	panel.add(label4);
	
	frame.setVisible(true);
	
	int count = 0;
	int color = 0;
	
	Color[] colors = new Color[] { Color.white, Color.lightGray };
	
	while (true) {
		if (count++ % 30 == 0) {
			label1.setBackground(colors[++color & 1]);
			label2.setBackground(colors[color + 1 & 1]);
			label3.setBackground(colors[color & 1]);
			label4.setBackground(colors[color & 1]);
		}
		Thread.sleep(16);
	}
}

In fact I don’t even see it when the labels are added straight to the JFrame without a JPanel.

BurntPizza, these shorter code samples don’t really show the problem, but my actual metronome app has a lot more code in between changing the display. I shortened this example down to the bare essentials of trying to get the double buffering to work. I didn’t think anyone would want to sort through my current 9 files of code. :smiley: I’m also in the process of reworking my program (based on some help I had in another thread) in order to implement the “loop” as I did it a totally different way before. But I was having issues with the display being off sync and I wanted to get that problem sorted out before I got to deep in rewriting everything.

What about if you insert a sleep(30):


label1.setBackground(colors[++color & 1]);
label2.setBackground(colors[color + 1 & 1]);
label3.setBackground(colors[color & 1]);
Thread.sleep(30);
label4.setBackground(colors[color & 1]);

Is that like what you’re experiencing, like what you mean by “a lot more code in between changing the display”? Update all states at the same time and you should be good, if there’s signifficant stuff executing in between the updating of different UI components, then yeah, that’s gonna look weird.

I have the feeling your problems with the display being off-sync has to do with your possibly incorrect usage of Swing, or more precisely the EDT.
If you put any long-running tasks into the EDT, (be it a complex calculation or the loading of a file), the entire GUI will start to behave really weird and get unresponsive. Making mistakes like not changing Swing-components on the EDT, or running a very expensive operation on the EDT, is the main reason for a lot of bugs. What’s even worse is constantly calling repaint() as fast as possible by using a dynamic-timestep application-loop, which is what I think you are doing (?).

Fix your application-loop so it doesn’t call the repaint() method more than ~30 times per second.

Why 30 times: Nobody can possibly read a number that changes more than 30 times per second (there are probably exceptions to that). Example: I can barely guess a number that changes 20 times per second, any faster and I am totally lost.

I doubt it is, but if the fact that the JLabels cause repaints is really your problem, there is a way to prevent it, although it’s a really nasty hack:


<snip>
		NColor[] colors = new NColor[] { new NColor(Color.white), new NColor(Color.lightGray) };
		
		while (true) {
			if (count++ % 30 == 0) {
				label1.setBackground(colors[++color & 1]);
				label2.setBackground(colors[color + 1 & 1]);
				label3.setBackground(colors[color & 1]);
				Thread.sleep(30);
				label4.setBackground(colors[color & 1]);
			}
			panel.repaint();
			Thread.sleep(16);
		}
	}
}

@SuppressWarnings("serial")
class NColor extends Color {
	
	public NColor(Color c) {
		super(c.getRGB());
	}
	
	@Override
	public boolean equals(Object obj) {
		return true; // AAHHH
	}
}

Note that the labels change their backgrounds in lockstep (the panel.repaint()) even though there’s that sleep(30) that from my previous example would normally cause a lag on label4.
I expect there is a similar hack for setText().

I do not recommend this at all, but you might try it just to see if that is what your problem actually is.

[quote=“Tekkerue,post:6,topic:50330”]
You should only be modifying the JLabels on the EDT.

Painting is done on the EDT as well. So are events.

That means that if you set the text (or icons, whatever) of both JLabels at the same time, neither JLabel will repaint before the other one is updated, because you’re using the EDT to update the JLabels, so painting can’t be started until you’re done.

You’re introducing a ton of needless complexity to this program, when really all you need are the basics.

Update the JLabels from the EDT. Let them paint themselves.

This is solid advise as well.
Another demo modification:

(back to normal colors, no NColor hacks)


<snip>
while (true) {
		if (count++ % 30 == 0) {
			color++;
			final int c = color;
			SwingUtilities.invokeLater(() -> {  // run update on EDT (you don't have to use JDK8)
				label1.setBackground(colors[c & 1]);
				label2.setBackground(colors[c + 1 & 1]);
				label3.setBackground(colors[c & 1]);
				try {
					Thread.sleep(30);
				} catch (Exception e) {
				}
				label4.setBackground(colors[c & 1]);
			});
		}
		panel.repaint();
		Thread.sleep(16);
	}

Updates in lockstep.

To demonstrate my points, here is a program that constantly updates two JLabels on the EDT and keeps them in sync simply by obeying the Swing rule that anything that changes the GUI (including your JLabels) must be done on the EDT:

Warning: this program might not be safe for people with epilepsy.

import java.awt.BorderLayout;
import java.awt.Color;

import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.SwingUtilities;


public class Test {
	public static void main (String [] args){

		JFrame frame = new JFrame("JLabel Test");
		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

		final JLabel topLabel = new JLabel("top");
		final JLabel bottomLabel = new JLabel("bottom");

		topLabel.setOpaque(true);
		bottomLabel.setOpaque(true);

		Thread t = new Thread(){
			public void run(){

				int steps = 0;

				while(true){

					//this is a lazy trick
					final String stepsText = "Steps: " + (++steps);

					//this will be done on the EDT so the whole thing will happen before any painting
					SwingUtilities.invokeLater(new Runnable(){
						public void run(){

							Color bg = new Color((int)(Math.random()*256), (int)(Math.random()*256), (int)(Math.random()*256));
							Color fg = new Color((int)(Math.random()*256), (int)(Math.random()*256), (int)(Math.random()*256));


							topLabel.setBackground(bg);
							topLabel.setForeground(fg);

							bottomLabel.setBackground(bg);
							bottomLabel.setForeground(fg);
							
							topLabel.setText(stepsText);
							bottomLabel.setText(stepsText);

						}
					});

					try {
						Thread.sleep(10);
					} 
					catch (InterruptedException e) {
						e.printStackTrace();
					}

				}
			}
		};


		frame.add(topLabel, BorderLayout.NORTH);
		frame.add(bottomLabel,BorderLayout.SOUTH);

		frame.setSize(200, 100);
		frame.setVisible(true);

		t.start();
	}
}

If you don’t believe me that they’re staying in sync, try taking a screenshot.

@Kevin Also see my example.

Whoops, all I saw was your snippet, and I wanted to provide a complete MCVE. I didn’t realize you did the same thing above!

To everyone mentioning the EDT,

Ahhhhh, yeah! That’s much better than the hack job that I was trying to do! :slight_smile:

So let’s see if I’ve worked this through correctly…instead of directly changing the text and background in my update method, I’d instead set a Boolean (to test whether or not to change the JLabel), Color, and String (which would both be sent to the JLabel) and then call another method which would use these variables to update the JLabel via the EDT. Correct?

BurntPizza,

Yeah, your example was pretty close to the delay I was getting, sometimes mine would be a little more actually. I also had to change your colors to alternate between yellow and green. The grey and white actually didn’t show the delay as well.

I ran your code on the EDT (like you posted later) and I even increased the delay…this works PERFECT no delay at all!

KevinWorkman,

Wow, that’s some serious seizure inducing flashing going on there! :o LOL

I believed you that they’re in sync, but I took a screen shot anyways. ;D It was perfect as expected!

Thank you all again everyone for your awesome responses. This helps immensely!!!

Hello again,

I have one more question…

I used BurntPizza’s example, tweaked it a bit and added an animation onto it using jpg’s. For my metronome program I’d also like to add a “swinging” metronome animation so I’m trying my hand at getting some kind of simple animation (only two frames) going along with updating the JLabels.

The only way I could get the animation to switch at the same time as the other JLabels was to use two separate jpg images, load them as ImageIcons, and then add them to a JLabel by calling setIcon in the invokeLater method. This method does work, but since I’m not experienced in animation I just wanted to make sure that this was an acceptable method to use for animation…or would I be better off using another method?

I did try the single “sprite sheet” method at first, but from the tutorials and examples I looked at this involved drawing inside of the paintComponent method and I wasn’t able to figure out how to get it that to work along with my JLabels. So I tried the ImageIcon method and that was fairly easy to get working.

Here is what I was able to get working…am I on the right path here? Thanks! :slight_smile:

Test.java

import java.awt.*;
import javax.swing.*;

public class Test extends JFrame{
	JPanel panel = new JPanel();
	final JLabel label1 = new JLabel("Label 1");
	final JLabel label2 = new JLabel("Label 2");
	JLabel animationLabel = new JLabel();
	
	final Color[] colors = new Color[] { Color.green, Color.yellow };
	final int[] position = { 100, 300 };
	
	int count = 0;
	int index = 0;
	
	ImageIcon[] animation = new ImageIcon[2]; 
	
	public static void main(String[] a) throws InterruptedException {
		Test test = new Test();
	}
	
	public Test() throws InterruptedException {
		animation[0] = new ImageIcon(getClass().getResource("dot1.jpg"));
		animation[1] = new ImageIcon(getClass().getResource("dot2.jpg"));
		
		setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		setSize(410, 300);
		setLocationRelativeTo(null);
		
		label1.setMinimumSize(new Dimension(100,100));
		label1.setPreferredSize(new Dimension(100,100));
		label1.setOpaque(true);
		
		label2.setMinimumSize(new Dimension(100,100));
		label2.setPreferredSize(new Dimension(100,100));
		label2.setOpaque(true);
		
		animationLabel.setMinimumSize(new Dimension(400,100));
		animationLabel.setPreferredSize(new Dimension(400,100));
		animationLabel.setOpaque(true);

		panel.setSize(new Dimension(400, 400));		
		panel.add(label1);
		panel.add(label2);
		panel.add(animationLabel);
		
		add(panel);
		setVisible(true);
		
		while (true) {
			if (count++ % 30 == 0) {
				final int i = index % 2;				
				SwingUtilities.invokeLater(new Runnable(){
					public void run(){
						label1.setBackground(colors[i]);
						label2.setBackground(colors[i]);
						animationLabel.setIcon(animation[i]);
					}
				});
				index++;
			}
			Thread.sleep(16);
		}
	}
}

And here are the two jpgs I used:

dot1.jpg

dot2.jpg

Here it is with the animation driving the labels via PropertyChangeListeners:

http://pastebin.java-gaming.org/171be99430117

I find the Observer pattern (EventListeners) quite suitable for this kind of thing.

Go the easy way. JPanel has [icode]setDoubleBuffered(boolean)[/icode] method.


public class GamePanel extends JPanel
{
    // [snip]
    public GamePanel()
    {
        super();

        setDoubleBuffered(true);
        setFocusable(true);
        requestFocus();
    }
    // [snip]
}

Then simply override the paint method and do your rendering.