且构网

分享程序员开发的那些事...
且构网 - 分享程序员编程开发的那些事

JPanel 上活动绘图顶部的 JTextFields,线程问题

更新时间:2023-12-05 09:00:22

NewTest extends JPanel;但是因为您没有在每次调用 paintComponent() 时绘制每个像素,所以您需要调用超类的方法并删除旧的绘图:

@Override受保护的无效paintComponent(图形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() 允许文本字段才能正常工作.正如您所观察到的,建议的解决方法是脆弱的.相反,应简化代码并根据需要进行优化.例如,您可能不需要复杂的插入、额外的缓冲区和组件侦听器.

附录 2:注意 super.paintComponent() 调用 UI 委托的 update()方法,用它的背景颜色填充指定的组件(如果它的 opaque 属性为真)."您可以使用 setOpaque(false) 来排除这种情况.

import 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://***.com/questions/3256941 */公共类 AnimationTest 扩展 JPanel 实现 ActionListener {私有静态最终整数宽 = 640;私有静态最终 int HIGH = 480;私有静态最终 int RADIUS = 25;私有静态最终 int 帧 = 24;private final Timer timer = new Timer(20, this);私有最终矩形矩形 = 新矩形();私有 BufferedImage 背景;私有整数索引;私人长总时间;私人长期平均时间;私有 int frameCount;公共静态无效主(字符串 [] args){EventQueue.invokeLater(new Runnable() {@覆盖公共无效运行(){new AnimationTest().create();}});}私有无效创建(){JFrame f = new JFrame("AnimationTest");f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);f.添加(这个);f.pack();f.setLocationRelativeTo(null);f.setVisible(true);定时器开始();}公共动画测试(){超级(真);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(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 * 半径,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);}@覆盖public void actionPerformed(ActionEvent e) {this.repaint();}私有类 MouseHandler 扩展 MouseAdapter {@覆盖public void mousePressed(MouseEvent e) {super.mousePressed(e);JTextField field = new JTextField("test");维度 d = field.getPreferredSize();field.setBounds(e.getX(), e.getY(), d.width, d.height);添加(字段);}}私有类 ComponentHandler 扩展了 ComponentAdapter {私有最终 GraphicsEnvironment ge =GraphicsEnvironment.getLocalGraphicsEnvironment();私有最终 GraphicsConfiguration gc =ge.getDefaultScreenDevice().getDefaultConfiguration();私人最终随机 r = 新随机();@覆盖public void componentResized(ComponentEvent 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 

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://***.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);
        }
    }
}