有没有人试图使用Swing来构建一个适当的多缓冲渲染环境,其上可以添加Swing用户界面元素?
在这种情况下,我有一个动画的红色矩形绘制到背景上.背景不需要每帧更新,所以我将其渲染到BufferedImage并重绘仅清除矩形的以前位置所需的部分.请参阅下面的完整代码,这扩展了前一个线程here中@trashgod给出的示例.
到现在为止还挺好;平滑动画,低cpu使用,不闪烁.
然后我添加一个JTextField到Jpanel(通过点击屏幕上的任何位置),并通过点击文本框内的焦点.清除矩形的上一个位置现在在每个光标闪烁时都会失败,请参见下图.
如果有人知道为什么会发生这种情况,我很好奇(Swing不是线程安全的,图像被异步绘制),以及在什么方向寻找可能的解决方案.
这是在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 } }
解决方法
NewTest扩展了JPanel;但是因为每次调用paintComponent()时都不会绘制每个像素,所以您需要调用超类的方法并擦除旧图形:
@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); ... }
附录:如你所说,在构造函数中设置背景颜色排除了在paintComponent()中填充面板的需要,而super.paintComponent()允许文本字段正常运行.如您所见,拟议的解决方法是脆弱的.相反,简化代码并进行优化.例如,您可能不需要复杂的插件,额外的缓冲区和组件监听器.
附录2:请注意,super.paintComponent()调用UI委托的update()
方法,“它将使用其背景颜色填充指定的组件(如果其opaque属性为true)”.您可以使用setOpaque(false)来排除这一点.
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); } } }