JPanel 上的活动绘图之上的 JTextFields,线程问题
有没有人尝试过使用 Swing 构建一个适当的多缓冲渲染环境在其之上可以添加 Swing 用户界面元素?
在这种情况下,我在背景上绘制了一个动画红色矩形.背景不需要每帧都更新,因此我将其渲染到 BufferedImage 上并仅重绘清除矩形先前位置所需的部分.请参阅下面的完整代码,这扩展了@trashgod 在上一个线程中给出的示例,
(来源:arttech.nl)
导入java.awt.Color;导入 java.awt.Dimension;导入 java.awt.EventQueue;导入 java.awt.Graphics;导入 java.awt.GraphicsConfiguration;导入 java.awt.GraphicsDevice;导入 java.awt.GraphicsEnvironment;导入 java.awt.Insets;导入 java.awt.Rectangle;导入 java.awt.Transparency;导入 java.awt.event.ActionEvent;导入 java.awt.event.ActionListener;导入 java.awt.event.ComponentEvent;导入 java.awt.event.ComponentListener;导入 java.awt.event.MouseEvent;导入 java.awt.event.MouseListener;导入 java.awt.image.BufferedImage;导入 javax.swing.JFrame;导入 javax.swing.JPanel;导入 javax.swing.JTextField;导入 javax.swing.Timer;公共类 NewTest 扩展了 JPanel 实现鼠标监听器,动作监听器,组件监听器,可运行{JFrame f;插图 插图;私人计时器 t = new Timer(20, this);缓冲图像缓冲区1;boolean repaintBuffer1 = true;int initWidth = 640;int initHeight = 480;矩形矩形;公共静态无效主要(字符串[]参数){EventQueue.invokeLater(new NewTest());}@覆盖公共无效运行(){f = new JFrame("NewTest");f.addComponentListener(this);f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);f.add(这个);f.pack();f.setLocationRelativeTo(null);f.setVisible(true);创建缓冲区();插图 = f.getInsets();t.start();}公共新测试(){超级(真);this.setPreferredSize(new Dimension(initWidth, initHeight));this.setLayout(null);this.addMouseListener(this);}无效创建缓冲区(){int 宽度 = this.getWidth();int 高度 = this.getHeight();GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();GraphicsDevice gs = ge.getDefaultScreenDevice();GraphicsConfiguration gc = gs.getDefaultConfiguration();buffer1 = gc.createCompatibleImage(宽度, 高度, 透明度.OPAQUE);重绘缓冲区1 =真;}@覆盖受保护的无效paintComponent(图形g){int 宽度 = this.getWidth();int 高度 = this.getHeight();if (repaintBuffer1) {图形 g1 = buffer1.getGraphics();g1.clearRect(0, 0, 宽度, 高度);g1.setColor(Color.green);g1.drawRect(0, 0, 宽度 - 1, 高度 - 1);g.drawImage(buffer1, 0, 0, null);重绘缓冲区 1 = 假;}双倍时间 = 2* Math.PI * (System.currentTimeMillis() % 5000)/5000.;g.setColor(Color.RED);如果(矩形!= 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,这个);}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);}@覆盖公共无效actionPerformed(ActionEvent e){this.repaint();}@覆盖公共无效组件隐藏(组件事件 arg0){//TODO 自动生成的方法存根}@覆盖公共无效组件移动(组件事件 arg0){//TODO 自动生成的方法存根}@覆盖公共无效组件调整(组件事件 e){int width = e.getComponent().getWidth() - (insets.left + insets.right);int height = e.getComponent().getHeight() - (insets.top + insets.bottom);this.setSize(宽度, 高度);创建缓冲区();}@覆盖公共无效组件显示(组件事件 arg0){//TODO 自动生成的方法存根}@覆盖公共无效鼠标点击(鼠标事件e){JTextField field = new JTextField("test");field.setBounds(new Rectangle(e.getX(), e.getY(), 100, 20));this.add(field);重绘缓冲区1 =真;}@覆盖公共无效鼠标输入(鼠标事件 arg0){//TODO 自动生成的方法存根}@覆盖公共无效mouseExited(MouseEvent arg0){//TODO 自动生成的方法存根}@覆盖公共无效mousePressed(MouseEvent arg0){//TODO 自动生成的方法存根}@覆盖公共无效mouseReleased(MouseEvent arg0){//TODO 自动生成的方法存根}}
解决方案 NewTest
extends JPanel
;但是因为您没有在每次调用 paintComponent()
时绘制每个像素,所以您需要调用超类的方法并擦除旧的绘图:
@Override受保护的无效paintComponent(图形g){super.paintComponent(g);int 宽度 = this.getWidth();int 高度 = this.getHeight();g.setColor(Color.black);g.fillRect(0, 0, 宽度, 高度);...}
附录:正如您所注意到的,在构造函数中设置背景颜色排除了在 paintComponent()
中填充面板的需要,而 super.paintComponent()
允许文本字段以正常工作.如您所见,建议的解决方法很脆弱.相反,简化代码并根据需要进行优化.例如,您可能不需要复杂的插入、额外缓冲区和组件侦听器.
附录 2:注意 super.paintComponent()
调用 UI 委托的 update()
方法,它用它的背景颜色填充指定的组件(如果它的 opaque 属性为真)."您可以使用 setOpaque(false)
来排除这种情况.
导入java.awt.Color;导入 java.awt.Dimension;导入 java.awt.EventQueue;导入 java.awt.Graphics;导入 java.awt.Graphics2D;导入 java.awt.GraphicsConfiguration;导入 java.awt.GraphicsEnvironment;导入 java.awt.Rectangle;导入 java.awt.Transparency;导入 java.awt.event.ActionEvent;导入 java.awt.event.ActionListener;导入 java.awt.event.ComponentAdapter;导入 java.awt.event.ComponentEvent;导入 java.awt.event.MouseAdapter;导入 java.awt.event.MouseEvent;导入 java.awt.image.BufferedImage;导入 java.util.Random;导入 javax.swing.JFrame;导入 javax.swing.JPanel;导入 javax.swing.JTextField;导入 javax.swing.Timer;/** @see http://stackoverflow.com/questions/3256941 */公共类 AnimationTest 扩展 JPanel 实现 ActionListener {私有静态最终 int WIDE = 640;私有静态最终 int HIGH = 480;私有静态最终 int RADIUS = 25;私有静态最终 int FRAMES = 24;私人最终计时器计时器=新计时器(20,这个);私人最终矩形矩形=新矩形();私有 BufferedImage 背景;私有 int 索引;私人长总时间;私人长平均时间;私有 int 帧计数;公共静态无效主要(字符串[]参数){EventQueue.invokeLater(new Runnable() {@覆盖公共无效运行(){新的 AnimationTest().create();}});}私人无效创建(){JFrame f = new JFrame("AnimationTest");f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);f.add(这个);f.pack();f.setLocationRelativeTo(null);f.setVisible(true);计时器.start();}公共动画测试(){超级(真);this.setOpaque(false);this.setPreferredSize(new Dimension(WIDE, HIGH));this.addMouseListener(new MouseHandler());this.addComponentListener(new ComponentHandler());}@覆盖受保护的无效paintComponent(图形g){长开始 = System.nanoTime();super.paintComponent(g);int w = this.getWidth();int h = this.getHeight();g.drawImage(背景, 0, 0, 这个);双 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 * 半径,2 * 半径);g.fillOval(rect.x, rect.y, rect.width, rect.height);g.setColor(Color.white);如果(帧数 == 帧){平均时间 = 总时间/帧数;总时间 = 0;帧数 = 0;} 别的 {totalTime += System.nanoTime() - 开始;帧数++;}String s = String.format("%1$5.3f", averageTime/1000000d);g.drawString(s, 5, 16);}@覆盖公共无效actionPerformed(ActionEvent e){this.repaint();}私有类 MouseHandler 扩展 MouseAdapter {@覆盖公共无效鼠标按下(鼠标事件e){super.mousePressed(e);JTextField field = new JTextField("test");维度 d = field.getPreferredSize();field.setBounds(e.getX(), e.getY(), d.width, d.height);添加(字段);}}私有类 ComponentHandler 扩展 ComponentAdapter {私有最终图形环境 ge =GraphicsEnvironment.getLocalGraphicsEnvironment();私有最终 GraphicsConfiguration gc =ge.getDefaultScreenDevice().getDefaultConfiguration();私人最终随机 r = new Random();@覆盖公共无效组件调整(组件事件 e){super.componentResized(e);int w = getWidth();int h = getHeight();背景 = 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("调整大小为 " + w + " x " + h);}}}
Has anyone ever tried to use Swing to construct a proper multi-buffered rendering environment on top of which Swing user interface elements can be added?
In this case I have an animating red rectangle drawn onto a background. The background does not need to be updated every frame so I render it onto a BufferedImage and redraw only the portion necessary to clear the previous location of the rectangle. See the complete code below, this extends the example given by @trashgod in a previous thread, here.
So far so good; smooth animation, low cpu usage, no flickering.
Then I add a JTextField to the Jpanel (by clicking on any position on the screen), and focus on it by clicking inside the text box. Clearing the previous location of the rectangle now fails on every cursor blink, see the image below.
I am curious if anyone has an idea of why this might happen (Swing not being thread-safe? The image being painted asynchronously?) and in what direction to look for possible solutions.
This is on Mac OS 10.5, Java 1.6
(source: arttech.nl)
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
extends JPanel
; but because you're not painting every pixel on each call to paintComponent()
, you need to invoke the super-class's method and erase the old drawing:
@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);
...
}
Addendum: As you note, setting the background color in the constructor precludes the need to fill the panel in paintComponent()
, while super.paintComponent()
allows the text field(s) to function correctly. As you observe, the proposed workaround is fragile. Instead, simplify the code and optimize as warranted. For example, you may not need the complication of insets, extra buffers and a component listener.
Addendum 2: Note that super.paintComponent()
calls the UI delegate's update()
method, "which fills the specified component with its background color (if its opaque property is true)." You can use setOpaque(false)
to preclude 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 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);
}
}
}
相关文章