Swing中的增量图形 [英] Incremental graphics in Swing

查看:107
本文介绍了Swing中的增量图形的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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



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

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

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

私有静态类TestPanel扩展JPanel {

私有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);
}
});

//在现实生活中,我们会做一些其他的东西,然后
//稍后有些东西想要为
添加一条蓝线// //

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


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

这是行不通的。蓝线出现,但没有红线。我是
猜测这是因为中的 repaint()绘制会导致所有
到当我画蓝线时重新开始,但我不确定;无论如何,我不知道如何去调用
paintComponent



另外,如果我在两个 p.draw 调用之间放置 Thread.sleep(1000),则
I don一秒钟都看不到红线。所以我完全不清楚
如何让我的图形出现在我想要的时候。



我已经在增量图形在Swing中,但
没有什么能帮助找到解决方案。我发现一篇Oracle文章
在AWT和Swing中绘画,讨论了重写一个 update()
方法来完成增量图形,但我没有' t找到了任何
的实际例子。



那么我怎样才能做到我想要的?这似乎是一个常见的
足够的任务,应该有一个简单的方法来做到这一点,但我没有找到一个
。我假设它不应该调用
getGraphics ,它基于其他的StackOverflow响应,最好是

解决方案

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 context,你可以添加并且只需要在你的<$ c
$ b $ p

这意味着,每次调用 p.draw(...)时, ,你实际上会先画到这个缓冲区,然后调用 repaint



有了这个,你需要保持缓冲区的大小。每次组件大小发生更改时,都需要根据组件的新大小将此缓冲区复制到新缓冲区。这是一个有点混乱,但是可行的。



另一个解决方案是将每个动作放在 List 在需要时,只需循环 List 并在需要时重新应用该操作。



这可能是最简单的方法,但随着行动数量的增加,可能会降低油漆过程的效率,减慢油漆过程。



您也可以将两者结合使用。当它不存在时生成一个缓冲区,遍历操作的 List 并将它们渲染到缓冲区,并简单地在 paintComponent 方法。无论何时调整组件大小,只需 null 缓冲区并允许 paintComponent 重新生成它...例如.. 。


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


Swing是一个单线程框架。这意味着所有的更新和修改都要在Event Dispatching Thread的上下文中完成。同样,阻止EDT运行的任何东西都会阻止它进行处理( $ b

这意味着当> sleep p之间时.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;
导入javax.swing.SwingUtilities;

public class DrawTest {

private interface GraphicsAction {

public void action(Graphics g);
}

私有静态类TestPanel扩展JPanel {

私有GraphicsAction paintAction;
专用BufferedImage缓冲区;

@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(); (


保护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();
}
返回缓冲区;
}

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

//在现实生活中,我们会做一些其他的东西,然后
//稍后有些东西想要为
添加一条蓝线// //
p.draw(new GraphicsAction(){
public void action(Graphics g){
g.setColor(Color.BLUE);
g.drawLine(5,30,150,40) ;
}
});


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


I'm trying to get the hang of doing graphics stuff (drawing lines, etc.) in Swing. So far, all the tutorials I've seen declare a class that overrides paintComponent, and all the paintComponent methods do some set, specific thing, like drawing a red square (although maybe they draw it at a different location every time). Or maybe they draw a number of lines and shapes, but the paintComponent method does everything all at once.

I'm trying to figure out: suppose I want to draw one thing in a component, and later on draw something else on top of it without erasing what I drew before. My first thought was to have my paintComponent override call a callback.

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();                 
            }
        });
    }
}

This doesn't work. A blue line shows up, but no red line. I'm guessing this is because the repaint() in draw causes everything to start over when I draw the blue line, but I'm not sure; anyway, I don't know how else to get paintComponent to be called.

Also, if I put a Thread.sleep(1000) between the two p.draw calls, I don't even see the red line for a second. So I'm not at all clear on how to get my graphics to show up when I want them to.

I've done some searching on "incremental graphics" in Swing, but nothing that helps find a solution. I found an Oracle article "Painting in AWT and Swing" that discusses overriding an update() method to accomplish incremental graphics, but I haven't found any actual examples of this being done.

So how would I get this to do what I want? It seems like a common enough task that there should be a simple way to do it, but I haven't found one. I'm assuming it should be doable without calling getGraphics, which, based on other StackOverflow responses would be, at best, kind of gauche.

解决方案

Painting in Swing is destructive. That is, whenever a new paint cycle runs, you are expected to completely rebuild the output as per the state of the object you are painting.

Take a look at Painting in AWT and Swing

So when you call

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

Followed by

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

You are basically throwing away the first action. Ignoring how repaints are scheduled for the moment. The first request says, "paint a red line", the second says, "paint a blue line", but before these actions are executed, the Graphics context is cleaned, preparing it for updating.

This is very important, as the Graphics context you are provided is a shared resource. All the components painted before have used the same context, all the components painted after you will use the same context. This means, if you don't "clean" the context before painting to it, you can end up with unwanted paint artifacts.

But how can you get around it??

There are a few choices here.

You could draw to a backing buffer (or BufferedImage) which has it's own Graphics context, which you can add to and would only need to "paint" in your paintComponent method.

This would mean, each time you call p.draw(...), you would actually paint to this buffer first then call repaint.

The problem with this, is you need to maintain the size of the buffer. Each time the component size changes, you would need to copy this buffer to a new buffer based on the new size of the component. This is a little messy, but is doable.

The other solution would be to place each action in a List and when required, simply loop through the List and re-apply the action whenever required.

This is probably the simplest approach, but as the number of actions grow, could reduce the effectiveness of the paint process, slowing the paint process.

You could also use a combination of the two. Generate a buffer when it doesn't exists, loop through the List of actions and renderer them to the buffer and simply paint the buffer in the paintComponent method. Whenever the component is resized, simply null the buffer and allow the paintComponent to regenerate it...for example...

Also, if I put a Thread.sleep(1000) between the two p.draw calls

Swing is a single threaded framework. That means that all updates and modifications are expected to done within the context of the Event Dispatching Thread.

Equally, anything that blocks the EDT from running will prevent it from process (amongst other things) paint requests.

This means that when you sleep between the p.draw calls, you are stopping the EDT from running, meaning it can't process your paint requests...

Take a look at Concurrency in Swing for more details

Updated with example

I just want to point out that is really inefficient. Re-creating the buffer each time invalidate is called will create a large number of short lived objects and could a significant drain on performance.

Normally, I would use a javax.swing.Timer, set to be non-repeating, that would be restarted each time invalidate was called. This would be set to a short delay (somewhere between 125-250 milliseconds). When the timer is triggered, I would simply re-construct the buffer at this time, but this is just an example ;)

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();
            }
        });
    }
}

这篇关于Swing中的增量图形的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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