无法在Swing中正确绘制自定义组件 [英] Can't paint a custom component in Swing correctly

查看:75
本文介绍了无法在Swing中正确绘制自定义组件的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我知道Swing非常差,所以很抱歉提出这个愚蠢的问题.我需要做的是制作我的自定义组件(它们是 JPanel 的祖先),并从缓冲区( BufferedImage 实例)执行绘画.这是一个要求,因为绘制过程可能非常繁琐,所以重写了paintComponent方法以从该缓冲区绘制并立即返回(但是如果您知道更好的方法来告诉Java,第二次消耗对象的时间不要超过200-300次)所有的CPU-我将不胜感激,也许有一些公共方法可以将Graphics上下文的区域标记为未更改",因此Swing不会尝试重新绘制它们.)

我的代码有什么问题,它根本不会在重叠的JPanels上绘制数据.我已经提出了一个可以重现该问题核心的例子.请参见下面的代码:

 公共类Dr扩展了JPanel {公共Dr(int x,int y,int w,int h,int fw,int fh){左= x;top = y;宽度= w;高度= h;full_width = fw;full_height = fh;setOpaque(true);}公共无效draw(){if(缓冲区==空&&宽度> 0&& amp;高度> 0){buffer = new BufferedImage(width,height,java.awt.image.BufferedImage.TYPE_INT_ARGB);}if(buffer == null)返回;图形g = buffer.getGraphics();setBounds(0,0,full_width,full_height);Graphics2D g2d =(Graphics2D)g;g2d.setBackground(Color.WHITE);g2d.clearRect(0,0,full_width,full_height);g2d.setColor(new Color(255,0,80,128));g2d.fillRect(left,top,width,height);System.out.println(left +," + top);}@Override公共无效的repaint(){画();}@Overridepublic void paintComponent(Graphics g){g.drawImage(buffer,0,0,null);}私有BufferedImage缓冲区;私人int;私人int顶层;私有int宽度;私有int高度;private int full_width;private int full_height;} 

这是一个示例类,除了保存他的坐标以绘制完整尺寸(我想等于其父尺寸)外,什么也不做,这又是一个要求,因为我需要支持可见的溢出内容才能显示),并绘制一个红色矩形,其透明度为100%的一半.

这是使用它的外部代码:

  Dr dr1 =新的Dr(4,4,width-10,80,width-2,height-2);Dr Dr2 =新Dr(4,88,宽10,80,宽2,高2);root.add(dr1);root.add(dr2);dr1.setBounds(0,0,width-2,height-2);dr2.setBounds(0,0,width-2,height-2);dr1.draw();dr2.draw(); 

预计会是什么:

当我删除缓冲区并直接在 paintComponent 方法中绘制时会显示什么:

当我不填充背景时显示什么(这里背景具有L& F系统颜色,但是我需要元素父级的颜色(在我的示例中是白色)

当我从缓冲区拖拉时会显示什么

最后一个完全模糊.从右侧和底部部分切除了红色矩形,并从父面板的(0,0)坐标中剪切了整个图像(包括白色背景).第二个对象根本不显示.控制台中的坐标绝对可以.

我觉得我与Swing有一些关系,要它在猜测要绘画什么和不绘画什么时不要执行它的魔术".但是我该怎么办呢?

更新:

我发现,即使我在添加子组件后不调用子组件的 draw()方法,它们仍会隐藏其父级的背景(这不应该发生,bacase JPanels旨在不透明).因此,核心问题是我不能使我的 Dr 对象具有不透明的背景(例如,没有背景),其中没有任何内容被绘画(这也解释了为什么我只能看到第一个矩形-z-Swing中的顺序似乎颠倒了,例如,最后添加的组件被绘制在底部,因为它离我们最远.)

解决方案

因此,我认为"您有一个基本的误解,那就是,一切在Swing中如何工作.

您似乎"对布局管理系统如何工作,绘画系统如何工作以及坐标系如何工作有疑问

让我们从您的 Dr 组件开始.

您似乎想开发重叠的组件,这些组件可以在其下面显示子组件,但是您使用的是 setOpaque(true); ,这意味着该组件不会被看到

这个...

  public void draw(){if(缓冲区==空&&宽度> 0&&高度> 0){buffer = new BufferedImage(width,height,java.awt.image.BufferedImage.TYPE_INT_ARGB);}if(buffer == null)返回;图形g = buffer.getGraphics();setBounds(0,0,full_width,full_height);Graphics2D g2d =(Graphics2D)g;g2d.setBackground(Color.WHITE);g2d.clearRect(0,0,full_width,full_height);g2d.setColor(new Color(255,0,80,128));g2d.fillRect(left,top,width,height);System.out.println(left +," + top);} 

对我来说似乎很奇怪.您将 BufferedImage 定义为通过 height 调整为 width 的大小,然后使用 full_width full_height 进行填充...在我看来,以另一种方式进行操作更有意义

  @Override公共无效的repaint(){画();} 

好的,这是Swing中的重要课程,您无法控制绘画过程,所以不要尝试.Swing具有完善的文档记录和完善的绘画过程,可提供定义明确的钩子,您可以在其中插入自定义绘画(即 paintComponent ).如果需要控制",则需要考虑使用 BufferStrategy ,它将为您提供完全的控制,以定义您自己的绘画过程.

好的,那答案是什么?

嗯,这不是那么简单,因为我不确定100%知道您要解决的问题是什么.

但是,让我们从 Dr 面板开始...

 公共类Dr扩展了JPanel {公共Dr(int x,int y,int w,int h,int fw,int fh){左= x;top = y;宽度= w;高度= h;full_width = fw;full_height = fh;setOpaque(false);setBounds(x,y,fw,fh);}公共无效draw(){if(缓冲区==空&&宽度> 0&&高度> 0){buffer = new BufferedImage(getWidth(),getHeight(),java.awt.image.BufferedImage.TYPE_INT_ARGB);}图形g = buffer.getGraphics();Graphics2D g2d =(Graphics2D)g;g2d.setColor(new Color(255,0,80,128));g2d.fillRect(0,0,width,height);g2d.dispose();}@Overridepublic void paintComponent(Graphics g){画();g.drawImage(buffer,0,0,this);g.setColor(Color.RED);g.drawRect(0,0,getWidth()-1,getHeight()-1);}私有BufferedImage缓冲区;私人int;私人int顶层;私有int宽度;私有int高度;private int full_width;private int full_height;} 

因此,在这里,我对其进行了更改,以便将面板放置在传递给构造函数的 x y 位置,并将其大小调整为 fw fh 属性.

然后在 draw 方法中,创建一个 BufferedImage ,其大小根据组件的当前大小进行绘制,并根据...的宽度绘制...和 height 属性...提出了有关为什么我们必须调整尺寸的问题,但是确实存在...

然后将缓冲区绘制到组件的顶部/左侧位置,这一点很重要,Swing的坐标系基于组件本身,因此 0x0 始终是组件顶部/左侧的角组件,坐标系与父容器无关.

ps-红色矩形用于调试目的,您不需要它.

然后我使用...

  EventQueue.invokeLater(new Runnable(){@Override公共无效run(){尝试 {UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());} catch(ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex){ex.printStackTrace();}JFrame frame = new JFrame("Testing");frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);frame.setContentPane(new JLayeredPane());frame.add(new Dr(10,10,180,180,200,200));frame.add(新Dr(100,100,180,180,200,200));frame.setSize(400,400);frame.setLocationRelativeTo(null);frame.setVisible(true);}}); 

瞧,重叠的组件...

现在,我强烈建议您停下来通读一遍:

而且,我的直觉是,您可能真的不想使用单独的组件,而您想要的是一个可以绘制许多缓冲区的组件

I know Swing very poorly, so sorry for stupid question. What I need to do is to make my custom components (they are ancestors of JPanel) with painting performed from the buffer (BufferedImage instance). It is a requirement, because the painting procedure might be very heavy, so paintComponent method is overridden to draw from that buffer and return imediately (but if you know better ways to tell Java not to repaint the object over 200-300 times a second consuming all the CPU - I would appreciate, maybe there are some public methods to mark regions of Graphics context as "not changed", so Swing does not try to repaint them).

What is wrong with my code that it simply does not draw data on overlapping JPanels. I have made a reproducable example of the core of the problem. See the code below:

public class Dr extends JPanel {

    public Dr(int x, int y, int w, int h, int fw, int fh) {
        left = x;
        top = y;
        width = w;
        height = h;
        full_width = fw;
        full_height = fh;
        setOpaque(true);
    }

    public void draw() {
        if (buffer == null && width > 0 && height > 0) {
            buffer = new BufferedImage(width, height, java.awt.image.BufferedImage.TYPE_INT_ARGB);
        }
        if (buffer == null) return;
        Graphics g = buffer.getGraphics();
        setBounds(0, 0, full_width, full_height);
        Graphics2D g2d = (Graphics2D)g;
        g2d.setBackground(Color.WHITE);
        g2d.clearRect(0, 0, full_width, full_height);

        g2d.setColor(new Color(255, 0, 80, 128));

        g2d.fillRect(left, top, width, height);
        System.out.println(left + ", " +  top);
    }

    @Override
    public void repaint() {
        draw();
    }

    @Override
    public void paintComponent(Graphics g) {
        g.drawImage(buffer, 0, 0, null);
    }

    private BufferedImage buffer;

    private int left;
    private int top;
    private int width;
    private int height;
    private int full_width;
    private int full_height;
}

This is a sample class that does nothing but saving his coordinates to draw at and full dimensions (which I want to be equal to its parent dimensions, that's a requirement again because I need to support visible overflowing content to be able to be displayed) and drawing a red rectangle with half of the 100% opacity.

Here is the outer code that uses it:

    Dr dr1 = new Dr(4, 4, width-10, 80, width-2, height-2);
    Dr dr2 = new Dr(4, 88, width-10, 80, width-2, height-2);
    root.add(dr1);
    root.add(dr2);
    dr1.setBounds(0, 0, width-2, height-2);
    dr2.setBounds(0, 0, width-2, height-2);
    dr1.draw();
    dr2.draw();

What is expected to be:

What is displayed when I remove my buffer and draw in paintComponent method directly:

What is displayed when I don't fill the background (here the background has system L&F color, but I need the color of element's parent (in my example it is white)

What is displayed when I drraw from my buffer

The last one is completely amuzing. The red rectangle is partly cut off from the right and from the bottom, and the full image (including the white background) is clipped from the (0, 0) coordinate of the parent panel. The second object is not displayed at all. The coordinates in the console are absolutely OK.

I feel there is something I have to do with Swing to tell it not to perform its "magic" upon guessing what to paint and what not to paint. But how can I do that?

UPDATE:

I found that even if I don't call draw() method of my child components after I added them, they still hide their parent's background (which should not happen, bacase JPanels are meant to be opaque). So the core problem is that I can't make my Dr objects have opaque background (e.g. no background) where nothing is painted in them (it also explains why I see only the first rectangle - the z-order in Swing seems to be reversed, e.g. the last added component is painted at the bottom, as it was the farthest from us).

解决方案

So, what I "think" you have is a fundamental misunderstanding how, well, everything works in Swing.

You "seem" to be having an issue with how the layout management system works, how the painting system works and how the coordinate system works

Let's start with your Dr component.

You seem to want to develop overlapping components, which can show the child components below them, but you use setOpaque(true);, which means that the component won't be see through

This...

public void draw() {
    if (buffer == null && width > 0 && height > 0) {
        buffer = new BufferedImage(width, height, java.awt.image.BufferedImage.TYPE_INT_ARGB);
    }
    if (buffer == null) return;
    Graphics g = buffer.getGraphics();
    setBounds(0, 0, full_width, full_height);
    Graphics2D g2d = (Graphics2D)g;
    g2d.setBackground(Color.WHITE);
    g2d.clearRect(0, 0, full_width, full_height);

    g2d.setColor(new Color(255, 0, 80, 128));

    g2d.fillRect(left, top, width, height);
    System.out.println(left + ", " +  top);
}

Seems odd to me. You define the BufferedImage to be sized as width by height, but then use full_width and full_height to fill it...it seems, to me, to make more sense to do it the other way round

@Override
public void repaint() {
    draw();
}

Okay, important lesson in Swing, you don't control the paint process, so don't try. Swing has a well documented and well established painting process that provides well defined hooks into which you can inject your custom painting (ie paintComponent). If you need "control", then you need to look towards using a BufferStrategy, which will give you complete control to define you're own painting process.

Okay, so what's the answer?

Well, that's not so straight forward, because I'm not 100% sure I understand what the problem is you're trying to solve is.

But, let's start with the Dr panel...

public class Dr extends JPanel {

    public Dr(int x, int y, int w, int h, int fw, int fh) {
        left = x;
        top = y;
        width = w;
        height = h;
        full_width = fw;
        full_height = fh;
        setOpaque(false);

        setBounds(x, y, fw, fh);
    }

    public void draw() {
        if (buffer == null && width > 0 && height > 0) {
            buffer = new BufferedImage(getWidth(), getHeight(), java.awt.image.BufferedImage.TYPE_INT_ARGB);
        }
        Graphics g = buffer.getGraphics();
        Graphics2D g2d = (Graphics2D) g;

        g2d.setColor(new Color(255, 0, 80, 128));

        g2d.fillRect(0, 0, width, height);
        g2d.dispose();
    }

    @Override
    public void paintComponent(Graphics g) {
        draw();
        g.drawImage(buffer, 0, 0, this);
        g.setColor(Color.RED);
        g.drawRect(0, 0, getWidth() - 1, getHeight() - 1);
    }

    private BufferedImage buffer;

    private int left;
    private int top;
    private int width;
    private int height;
    private int full_width;
    private int full_height;
}

So, here, I've changed it so that the panel will be positioned at the x, y position you pass to the constructor and will be sized to the fw and fh properties.

In the draw method, I then create a BufferedImage sized to the component's current size and paint ... what ever ... based on the width and height properties ... questions are raised about why we have to sizes, but, there it is...

The buffer is then draw to the top/left position of the component, this is important, Swing's coordinate system is based around the component itself, so 0x0 is always the top/left corner of the component, the coordinate system has nothing to do with the parent container.

ps- The red rectangle is for debugging purposes, you don't need it.

I then use...

EventQueue.invokeLater(new Runnable() {
    @Override
    public void run() {
        try {
            UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
        } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
            ex.printStackTrace();
        }

        JFrame frame = new JFrame("Testing");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setContentPane(new JLayeredPane());
        frame.add(new Dr(10, 10, 180, 180, 200, 200));
        frame.add(new Dr(100, 100, 180, 180, 200, 200));
        frame.setSize(400, 400);
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
    }
});

And voila, overlapping components...

Now, I strongly recommend that you stop and go have a read through:

And, my gut feeling is, you probably really don't want separate components, what you want is one component which can paint many buffers

这篇关于无法在Swing中正确绘制自定义组件的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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