Java – jtextfields: thread problem in activity drawing on JPanel
Has anyone tried to use swing to build an appropriate multi buffer rendering environment on which swing user interface elements can be added?
In this case, I have an animated red rectangle drawn on the background The background doesn't need to be updated every frame, so I render it to bufferedimage and redraw only the part needed to clear the previous position of the rectangle See the complete code below, which extends the example given in @ trashgod in the previous thread here
So far, it's very good; Smooth animation, low CPU usage, no flicker
Then I add a jtextfield to the JPanel (by clicking anywhere on the screen) and by clicking the focus in the text box Clearing the previous position of the rectangle now fails when each cursor flashes, as shown in the figure below
If anyone knows why this happens, I'm curious (swing is not thread safe, images are drawn asynchronously) and where to look for possible solutions
This is in Mac OS 10.5, Java 1.6
JPanel redraw fail http://www.arttech.nl/javaredrawerror.png
import java.awt.Color; import java.awt.Dimension; import java.awt.EventQueue; import java.awt.Graphics; import java.awt.GraphicsConfiguration; import java.awt.GraphicsDevice; import java.awt.GraphicsEnvironment; import java.awt.Insets; import java.awt.Rectangle; import java.awt.Transparency; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.ComponentEvent; import java.awt.event.ComponentListener; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.awt.image.BufferedImage; import javax.swing.JFrame; import javax.swing.JPanel; import javax.swing.JTextField; import javax.swing.Timer; public class NewTest extends JPanel implements MouseListener,ActionListener,ComponentListener,Runnable { JFrame f; Insets insets; private Timer t = new Timer(20,this); BufferedImage buffer1; boolean repaintBuffer1 = true; int initWidth = 640; int initHeight = 480; Rectangle rect; public static void main(String[] args) { EventQueue.invokelater(new Newtest()); } @Override public void run() { f = new JFrame("NewTest"); f.addComponentListener(this); f.setDefaultCloSEOperation(JFrame.EXIT_ON_CLOSE); f.add(this); f.pack(); f.setLocationRelativeTo(null); f.setVisible(true); createBuffers(); insets = f.getInsets(); t.start(); } public Newtest() { super(true); this.setPreferredSize(new Dimension(initWidth,initHeight)); this.setLayout(null); this.addMouseListener(this); } void createBuffers() { int width = this.getWidth(); int height = this.getHeight(); GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment(); GraphicsDevice gs = ge.getDefaultScreenDevice(); GraphicsConfiguration gc = gs.getDefaultConfiguration(); buffer1 = gc.createCompatibleImage(width,height,Transparency.OPAQUE); repaintBuffer1 = true; } @Override protected void paintComponent(Graphics g) { int width = this.getWidth(); int height = this.getHeight(); if (repaintBuffer1) { Graphics g1 = buffer1.getGraphics(); g1.clearRect(0,width,height); g1.setColor(Color.green); g1.drawRect(0,width - 1,height - 1); g.drawImage(buffer1,null); repaintBuffer1 = false; } double time = 2* Math.PI * (System.currentTimeMillis() % 5000) / 5000.; g.setColor(Color.RED); if (rect != null) { g.drawImage(buffer1,rect.x,rect.y,rect.x + rect.width,rect.y + rect.height,this); } rect = new Rectangle((int)(Math.sin(time) * width/3 + width/2 - 20),(int)(Math.cos(time) * height/3 + height/2) - 20,40,40); g.fillRect(rect.x,rect.width,rect.height); } @Override public void actionPerformed(ActionEvent e) { this.repaint(); } @Override public void componentHidden(ComponentEvent arg0) { // TODO Auto-generated method stub } @Override public void componentMoved(ComponentEvent arg0) { // TODO Auto-generated method stub } @Override public void componentResized(ComponentEvent e) { int width = e.getComponent().getWidth() - (insets.left + insets.right); int height = e.getComponent().getHeight() - (insets.top + insets.bottom); this.setSize(width,height); createBuffers(); } @Override public void componentShown(ComponentEvent arg0) { // TODO Auto-generated method stub } @Override public void mouseClicked(MouseEvent e) { JTextField field = new JTextField("test"); field.setBounds(new Rectangle(e.getX(),e.getY(),100,20)); this.add(field); repaintBuffer1 = true; } @Override public void mouseEntered(MouseEvent arg0) { // TODO Auto-generated method stub } @Override public void mouseExited(MouseEvent arg0) { // TODO Auto-generated method stub } @Override public void mousePressed(MouseEvent arg0) { // TODO Auto-generated method stub } @Override public void mouseReleased(MouseEvent arg0) { // TODO Auto-generated method stub } }
Solution
Newtest extends JPanel; However, because each pixel will not be drawn every time paintcomponent() is called, you need to call the superclass method and erase the old graphics:
@Override protected void paintComponent(Graphics g) { super.paintComponent(g); int width = this.getWidth(); int height = this.getHeight(); g.setColor(Color.black); g.fillRect(0,height); ... }
Appendix: as you said, setting the background color in the constructor eliminates the need to fill the panel in paintcomponent(), and super Paintcomponent() allows text fields to function properly As you can see, the proposed solution is fragile Instead, simplify the code and optimize it For example, you may not need complex plug - ins, additional buffers, and component listeners
Appendix 2: Please note that super Paintcomponent() calls the update() method of the UI delegate, "it will fill the specified component with its background color (if its opaque attribute is true)" You can use setopaque (false) to exclude this
import java.awt.Color; import java.awt.Dimension; import java.awt.EventQueue; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.GraphicsConfiguration; import java.awt.GraphicsEnvironment; import java.awt.Rectangle; import java.awt.Transparency; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.ComponentAdapter; import java.awt.event.ComponentEvent; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.image.BufferedImage; import java.util.Random; import javax.swing.JFrame; import javax.swing.JPanel; import javax.swing.JTextField; import javax.swing.Timer; /** @see https://stackoverflow.com/questions/3256941 */ public class AnimationTest extends JPanel implements ActionListener { private static final int WIDE = 640; private static final int HIGH = 480; private static final int RADIUS = 25; private static final int FRAMES = 24; private final Timer timer = new Timer(20,this); private final Rectangle rect = new Rectangle(); private BufferedImage background; private int index; private long totalTime; private long averageTime; private int frameCount; public static void main(String[] args) { EventQueue.invokelater(new Runnable() { @Override public void run() { new Animationtest().create(); } }); } private void create() { JFrame f = new JFrame("AnimationTest"); f.setDefaultCloSEOperation(JFrame.EXIT_ON_CLOSE); f.add(this); f.pack(); f.setLocationRelativeTo(null); f.setVisible(true); timer.start(); } public Animationtest() { super(true); this.setOpaque(false); this.setPreferredSize(new Dimension(WIDE,HIGH)); this.addMouseListener(new MouseHandler()); this.addComponentListener(new ComponentHandler()); } @Override protected void paintComponent(Graphics g) { long start = System.nanoTime(); super.paintComponent(g); int w = this.getWidth(); int h = this.getHeight(); g.drawImage(background,this); double theta = 2 * Math.PI * index++ / 64; g.setColor(Color.blue); rect.setRect( (int) (Math.sin(theta) * w / 3 + w / 2 - RADIUS),(int) (Math.cos(theta) * h / 3 + h / 2 - RADIUS),2 * RADIUS,2 * RADIUS); g.fillOval(rect.x,rect.height); g.setColor(Color.white); if (frameCount == FRAMES) { averageTime = totalTime / FRAMES; totalTime = 0; frameCount = 0; } else { totalTime += System.nanoTime() - start; frameCount++; } String s = String.format("%1$5.3f",averageTime / 1000000d); g.drawString(s,5,16); } @Override public void actionPerformed(ActionEvent e) { this.repaint(); } private class MouseHandler extends MouseAdapter { @Override public void mousePressed(MouseEvent e) { super.mousePressed(e); JTextField field = new JTextField("test"); Dimension d = field.getPreferredSize(); field.setBounds(e.getX(),d.width,d.height); add(field); } } private class ComponentHandler extends ComponentAdapter { private final GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment(); private final GraphicsConfiguration gc = ge.getDefaultScreenDevice().getDefaultConfiguration(); private final Random r = new Random(); @Override public void componentResized(ComponentEvent e) { super.componentResized(e); int w = getWidth(); int h = getHeight(); background = gc.createCompatibleImage(w,h,Transparency.OPAQUE); Graphics2D g = background.createGraphics(); g.clearRect(0,w,h); g.setColor(Color.green.darker()); for (int i = 0; i < 128; i++) { g.drawLine(w / 2,h / 2,r.nextInt(w),r.nextInt(h)); } g.dispose(); System.out.println("Resized to " + w + " x " + h); } } }