JPanel 上活动绘图顶部的 JTextFields,线程问题 [英] JTextFields on top of active drawing on JPanel, threading problems

查看:25
本文介绍了JPanel 上活动绘图顶部的 JTextFields,线程问题的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

有没有人尝试过使用 Swing 来构建合适的多缓冲渲染环境在其上可以添加 Swing 用户界面元素?

在这种情况下,我在背景上绘制了一个动画红色矩形.背景不需要每帧都更新,所以我将它渲染到 BufferedImage 并只重绘清除矩形先前位置所需的部分.请参阅下面的完整代码,这扩展了@trashgod 在上一个线程中给出的示例,
(来源:arttech.nl)

import 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 = 新定时器(20,这个);BufferedImage buffer1;boolean repaintBuffer1 = true;int initWidth = 640;int initHeight = 480;矩形矩形;公共静态无效主(字符串 [] args){EventQueue.invokeLater(new NewTest());}@覆盖公共无效运行(){f = new JFrame("NewTest");f.addComponentListener(this);f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);f.添加(这个);f.pack();f.setLocationRelativeTo(null);f.setVisible(true);创建缓冲区();insets = f.getInsets();t.start();}公共新测试(){超级(真);this.setPreferredSize(new Dimension(initWidth, initHeight));this.setLayout(null);this.addMouseListener(this);}无效创建缓冲区(){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 = 真;}@覆盖受保护的无效paintComponent(图形g){int width = this.getWidth();int height = this.getHeight();如果(重绘缓冲区1){图形 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 = 假;}双倍时间 = 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);}@覆盖public void actionPerformed(ActionEvent e) {this.repaint();}@覆盖公共无效组件隐藏(组件事件 arg0){//TODO 自动生成的方法存根}@覆盖公共无效componentMoved(ComponentEvent arg0){//TODO 自动生成的方法存根}@覆盖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);创建缓冲区();}@覆盖公共无效组件显示(组件事件 arg0){//TODO 自动生成的方法存根}@覆盖public void mouseClicked(MouseEvent e) {JTextField field = new JTextField("test");field.setBounds(new Rectangle(e.getX(), e.getY(), 100, 20));this.add(field);repaintBuffer1 = 真;}@覆盖公共无效鼠标输入(鼠标事件 arg0){//TODO 自动生成的方法存根}@覆盖公共无效鼠标退出(鼠标事件 arg0){//TODO 自动生成的方法存根}@覆盖公共无效鼠标按下(鼠标事件 arg0){//TODO 自动生成的方法存根}@覆盖公共无效鼠标释放(鼠标事件 arg0){//TODO 自动生成的方法存根}}

解决方案

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

这篇关于JPanel 上活动绘图顶部的 JTextFields,线程问题的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆