JTextFields在JPanel上的活动绘图之上,线程问题

有没有人曾经试图使用Swing构build一个适当的多缓冲渲染环境, 在其上面可以添加Swing用户界面元素

在这种情况下,我有一个在背景上绘制的animation红色矩形。 背景不需要每一帧更新,所以我把它渲染到一个BufferedImage上,只重绘清除矩形前一个位置所需的部分。 请参阅下面的完整代码,这扩展了上一个线程中的@trashgod给出的示例。

到现在为止还挺好; animationstream畅,CPU使用率低,不闪烁。

然后,我将JTextField添加到Jpanel(通过单击屏幕上的任何位置),然后通过在文本框内单击来关注它。 清除矩形的前一个位置现在在每个光标闪烁时都失败,请参阅下面的图像。

我很好奇,如果有人知道为什么会发生这种情况(Swing不是线程安全的?图像被asynchronous绘制?)以及在什么方向寻找可能的解决scheme。

这是在Mac OS 10.5,Java 1.6上

JPanel重绘失败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, 0, width, height); g1.setColor(Color.green); g1.drawRect(0, 0, width - 1, height - 1); g.drawImage(buffer1, 0, 0, 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, 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.y, 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, 0, width, height); ... } 

附录:正如你注意到的那样,在构造函数中设置背景颜色排除了在paintComponent()填充面板的必要,而super.paintComponent()允许文本字段正常工作。 如您所见,build议的解决方法是脆弱的。 相反,简化代码并按照需要进行优化。 例如,您可能不需要insets,额外的缓冲区和组件侦听器的复杂性。

附录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 http://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, 0, 0, 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.y, rect.width, 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(), e.getY(), 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, 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); } } } 

我find了一个解决方法。

我认为发生了什么事情:每当需要更新JTextfield(即在每个光标闪烁)时,JPanel被重写的paintComponent()被调用,但是与由repaint()调用的Graphics参数不同。 因此,在每个光标闪烁的情况下,我的矩形被清除并重绘在一个错误的graphics实例上,使屏幕上看到的graphics无效。

这是否有意义? 如果是这样,在Swing中这不是一个奇怪的不便?

无论如何,保持一个布尔( activeRedraw )的调用来自哪里,它看起来像我设法解决这个问题。 所以看来我终于find了一种方法来做主动绘图而不重绘每个画面上的整个屏幕区域,这意味着低CPU使用率的独立于窗口大小!

完整代码在这里:

 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; boolean activeRedraw = true; 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) { //super.paintComponent(g); int width = this.getWidth(); int height = this.getHeight(); if (activeRedraw) { if (repaintBuffer1) { Graphics g1 = buffer1.getGraphics(); g1.clearRect(0, 0, width, height); g1.setColor(Color.green); g1.drawRect(0, 0, width - 1, height - 1); g.drawImage(buffer1, 0, 0, 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, 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.y, rect.width, rect.height); activeRedraw = false; } } @Override public void actionPerformed(ActionEvent e) { activeRedraw = true; 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 } }