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

发布于 2024-09-10 05:03:20 字数 4888 浏览 3 评论 0原文

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

在本例中,我在背景上绘制了一个动画红色矩形。背景不需要每帧都更新,因此我将其渲染到 BufferedImage 上,并仅重绘清除矩形先前位置所需的部分。请参阅下面的完整代码,这扩展了 @trashgod 在上一个线程中给出的示例, 这里

到目前为止,一切都很好;动画流畅,CPU占用率低,无闪烁。

然后,我将 JTextField 添加到 Jpanel(通过单击屏幕上的任意位置),并通过单击文本框内部来聚焦于它。现在,每次光标闪烁时,清除矩形以前的位置都会失败,请参见下图。

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

这是在 Mac OS 10.5、Java 1.6 上

JPanel重绘失败
(来源: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

}
}

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

JPanel redraw fail
(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

}
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

路弥 2024-09-17 05:03:20

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() 允许文本字段正确运行。正如您所观察到的,提议的解决方法很脆弱。相反,应简化代码并根据需要进行优化。例如,您可能不需要复杂的插入、额外缓冲区和组件侦听器。

附录 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);
        }
    }
}

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.

Animation Test

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);
        }
    }
}
雪化雨蝶 2024-09-17 05:03:20

我找到了一个解决方法。

我认为发生了什么:每当需要更新 JTextfield 时(即每次光标闪烁时),都会调用 JPanel 的覆盖的 PaintComponent() ,但使用与 repaint() 调用时不同的 Graphics 参数。因此,每次光标闪烁时,我的矩形都会被清除并在错误的 Graphics 实例上重新绘制,从而使屏幕上看到的 Graphics 无效。

这有什么意义吗?如果确实如此,这对 Swing 来说不是一个奇怪的不便吗?

无论如何,通过保留调用来源的布尔值(activeRedraw),看起来我设法解决了这个问题。所以看来我终于找到了一种方法来进行主动绘图,而无需在每一帧上重新绘制整个屏幕区域,这意味着独立于窗口大小的低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

    }
}

I found a workaround.

What I think was happening: whenever a JTextfield needs to be updated (i.e. on every cursor blink), JPanel's overridden paintComponent() is called, but with a different Graphics argument than when called by repaint(). Thus on every cursor blink, my rectangle was cleared and redrawn on a wrong Graphics instance, leaving the Graphics seen on screen invalid.

Does this make any sense? If it does, isn't this a weird inconvenience in Swing?

Anyway, by keeping a boolean (activeRedraw) of where the invoke comes from, it looks like I managed to work around this issue. So it seems I finally found a way to do active drawing without repainting the entire screen area on every frame, which means low cpu usage independent of the window size!

Complete code here:

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

    }
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文