3

我试图掌握在 Swing 中做图形的技巧(画线等)。到目前为止,我看到的所有教程都声明了一个覆盖的类paintComponent,并且所有paintComponent方法都做了一些设置的、特定的事情,比如绘制一个红色方块(尽管他们可能每次都在不同的位置绘制它)。或者,也许他们画了许多线条和形状,但该paintComponent方法一次完成了所有事情。

我试图弄清楚:假设我想在一个组件中绘制一个东西,然后在它上面绘制其他东西,而不擦除我之前绘制的东西。我的第一个想法是让我的 paintComponent覆盖调用回调。

import java.awt.*;
import javax.swing.*;
public class DrawTest {

    private interface GraphicsAction {
        public void action (Graphics g);
    }

    private static class TestPanel extends JPanel {

        private GraphicsAction paintAction;

        public void draw (GraphicsAction action) {
            paintAction = action;
            repaint();
        }

        @Override
        public void paintComponent (Graphics g) {
            super.paintComponent (g);
            if (paintAction != null)
                paintAction.action(g);
        }
    }

    private static void createAndShowGui() {
        JFrame frame = new JFrame ("DrawTest");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setPreferredSize(new Dimension(500,500));

        TestPanel p = new TestPanel ();
        frame.getContentPane().add(p);
        frame.pack();
        frame.setVisible(true);
        p.repaint();

        p.draw (new GraphicsAction () {
            public void action (Graphics g) {
                g.setColor(Color.RED);
                g.drawLine(5, 30, 100, 50);
            }
        });

        // in real life, we would do some other stuff and then
        // later something would want to add a blue line to the
        // diagram 

        p.draw (new GraphicsAction () {
            public void action (Graphics g) {
                g.setColor(Color.BLUE);
                g.drawLine(5, 30, 150, 40);
            }
        });

    }

    public static void main (String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                createAndShowGui();                 
            }
        });
    }
}

这行不通。出现蓝线,但没有红线。我猜这是因为当我画蓝线时repaint()indraw会导致一切重新开始,但我不确定;无论如何,我不知道怎么 paintComponent才能被调用。

另外,如果我Thread.sleep(1000)在两个p.draw电话之间加上一个,我什至一秒钟都看不到红线。所以我完全不清楚如何让我的图形在我想要的时候显示出来。

我在 Swing 中对“增量图形”进行了一些搜索,但没有任何帮助找到解决方案。我找到了一篇 Oracle 文章“在 AWT 和 Swing 中绘画”,其中讨论了覆盖update() 实现增量图形的方法,但我还没有找到任何实际示例。

那么我怎么才能让它做我想做的事呢?这似乎是一项足够常见的任务,应该有一种简单的方法来完成它,但我还没有找到。我假设它应该在不调用的情况下是可行 getGraphics的,根据其他 StackOverflow 响应,这充其量只是一种粗略。

4

1 回答 1

6

Swing 中的绘画具有破坏性。也就是说,每当运行新的绘制周期时,您都应该根据正在绘制的对象的状态完全重建输出。

看看在 AWT 和 Swing 中的绘画

所以当你打电话

p.draw (new GraphicsAction () {
    public void action (Graphics g) {
        g.setColor(Color.RED);
        g.drawLine(5, 30, 100, 50);
    }
});

其次是

p.draw (new GraphicsAction () {
    public void action (Graphics g) {
        g.setColor(Color.BLUE);
        g.drawLine(5, 30, 150, 40);
    }
});

你基本上是在抛弃第一个动作。暂时忽略如何安排重绘。第一个请求说,“画一条红线”,第二个说,“画一条蓝线”,但在执行这些操作之前,Graphics上下文被清理,准备更新。

这非常重要,因为Graphics您提供的上下文是共享资源。之前绘制的所有组件都使用相同的上下文,之后绘制的所有组件都将使用相同的上下文。这意味着,如果您在绘制上下文之前不“清理”上下文,您最终可能会得到不需要的绘制伪像。

但是你怎么能绕过它?

这里有几个选择。

BufferedImage您可以绘制到具有自己的上下文的后备缓冲区(或) Graphics,您可以将其添加到并且只需要在您的paintComponent方法中“绘制”。

这意味着,每次您调用 时p.draw(...),您实际上都会先绘制到此缓冲区,然后再调用repaint

问题在于,您需要保持缓冲区的大小。每次组件大小发生变化时,您都需要根据组件的新大小将此缓冲区复制到新缓冲区。这有点混乱,但可行。

另一种解决方案是将每个操作放在 a 中List,并在需要时简单地循环并在需要时List重新应用该操作。

这可能是最简单的方法,但随着动作数量的增加,可能会降低绘制过程的有效性,从而减慢绘制过程。

您也可以将两者结合使用。当它不存在时生成一个缓冲区,循环通过List动作并将它们渲染到缓冲区并简单地在paintComponent方法中绘制缓冲区。每当调整组件大小时,只需null缓冲区并允许paintComponent重新生成它......例如......

另外,如果我在两个 p.draw 调用之间放置一个 Thread.sleep(1000)

Swing 是一个单线程框架。这意味着所有更新和修改都应该在事件调度线程的上下文中完成。

同样,任何阻止 EDT 运行的东西都会阻止它处理(除其他外)绘制请求。

这意味着当您sleepp.draw两次通话之间时,您正在停止 EDT 运行,这意味着它无法处理您的绘画请求......

查看Swing 中的并发以获取更多详细信息

更新了示例

在此处输入图像描述

我只想指出这真的是低效的。每次invalidate调用都重新创建缓冲区将创建大量短期对象,并可能显着降低性能。

通常,我会使用javax.swing.Timer, 设置为非重复,每次invalidate调用时都会重新启动。这将被设置为一个短暂的延迟(大约在 125-250 毫秒之间)。当定时器被触发时,我会在这个时候简单地重新构建缓冲区,但这只是一个例子;)

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;

public class DrawTest {

    private interface GraphicsAction {

        public void action(Graphics g);
    }

    private static class TestPanel extends JPanel {

        private GraphicsAction paintAction;
        private BufferedImage buffer;

        @Override
        public void invalidate() {
            BufferedImage img = new BufferedImage(
                    Math.max(1, getWidth()),
                    Math.max(1, getHeight()), BufferedImage.TYPE_INT_ARGB);
            Graphics2D g2d = img.createGraphics();
            g2d.setColor(getBackground());
            g2d.fillRect(0, 0, getWidth(), getHeight());
            if (buffer != null) {
                g2d.drawImage(buffer, 0, 0, this);
            }
            g2d.dispose();
            buffer = img;
            super.invalidate();
        }

        protected BufferedImage getBuffer() {
            if (buffer == null) {
                buffer = new BufferedImage(
                        Math.max(1, getWidth()),
                        Math.max(1, getHeight()), BufferedImage.TYPE_INT_ARGB);
                Graphics2D g2d = buffer.createGraphics();
                g2d.setColor(getBackground());
                g2d.fillRect(0, 0, getWidth(), getHeight());
                g2d.dispose();
            }
            return buffer;
        }

        public void draw(GraphicsAction action) {
            BufferedImage buffer = getBuffer();
            Graphics2D g2d = buffer.createGraphics();
            action.action(g2d);
            g2d.dispose();
            repaint();
        }

        @Override
        public void paintComponent(Graphics g) {
            super.paintComponent(g);
            g.drawImage(getBuffer(), 0, 0, this);
        }
    }

    private static void createAndShowGui() {
        JFrame frame = new JFrame("DrawTest");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setPreferredSize(new Dimension(500, 500));

        TestPanel p = new TestPanel();
        frame.getContentPane().add(p);
        frame.pack();
        frame.setVisible(true);
        p.repaint();

        p.draw(new GraphicsAction() {
            public void action(Graphics g) {
                g.setColor(Color.RED);
                g.drawLine(5, 30, 100, 50);
            }
        });

        // in real life, we would do some other stuff and then
        // later something would want to add a blue line to the
        // diagram 
        p.draw(new GraphicsAction() {
            public void action(Graphics g) {
                g.setColor(Color.BLUE);
                g.drawLine(5, 30, 150, 40);
            }
        });

    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                createAndShowGui();
            }
        });
    }
}
于 2013-09-17T01:36:05.407 回答