简体   繁体   English

无法在Swing中正确绘制自定义组件

[英]Can't paint a custom component in Swing correctly

I know Swing very poorly, so sorry for stupid question. 我知道Swing非常差,所以很抱歉这个愚蠢的问题。 What I need to do is to make my custom components (they are ancestors of JPanel ) with painting performed from the buffer ( BufferedImage instance). 我需要做的是使我的自定义组件(它们是JPanel祖先)具有从缓冲区( BufferedImage实例)执行的绘制。 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). 这是一个要求,因为绘制过程可能非常繁琐,所以重写了PaintComponent方法以从该缓冲区绘制并立即返回(但是如果您知道更好的方法来告诉Java,第二次消耗对象的时间不应超过200-300次)所有的CPU-我将不胜感激,也许有一些公共方法可以将Graphics上下文的区域标记为“未更改”,因此Swing不会尝试重新绘制它们)。

What is wrong with my code that it simply does not draw data on overlapping JPanels. 我的代码有什么问题,它根本不会在重叠的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. 这是一个示例类,除了保存他的坐标以绘制完整尺寸(我想等于其父尺寸)外,什么也不做,这又是一个要求,因为我需要支持可见的溢出内容才能显示)绘制一个红色矩形,其不透明度为100%的一半。

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: 当我删除缓冲区并直接绘制paintComponent方法时显示的内容:

直接油漆

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) 当我不填充背景时显示的内容(这里的背景具有系统L&F颜色,但是我需要元素父级的颜色(在我的示例中是白色)

直接上漆,无背景填充

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. 从右侧和底部部分切除了红色矩形,并从父面板的(0,0)坐标中剪切了整个图像(包括白色背景)。 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. 我觉得我与Swing有一些关系,要它在猜测要绘画什么和不绘画什么时告诉它不要执行其“魔术”。 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). 我发现,即使我在添加子组件之后不对其子集的draw()方法进行调用,它们仍会隐藏其父级的背景(这不应该发生,因为bacase JPanels应该是不透明的)。 So the core problem is that I can't make my Dr objects have opaque background (eg 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, eg the last added component is painted at the bottom, as it was the farthest from us). 因此,核心问题是我无法使我的Dr对象具有不透明的背景(例如,没有背景),其中没有任何内容被绘画(这也解释了为什么我只看到第一个矩形-Swing中的z顺序似乎被反转了) ,例如,最后添加的组件画在底部,因为它离我们最远。

So, what I "think" you have is a fundamental misunderstanding how, well, everything works in Swing. 因此,我“认为”您有一个根本性的误解,那就是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. 让我们从您的Dr组件开始。

You seem to want to develop overlapping components, which can show the child components below them, but you use setOpaque(true); 您似乎想开发重叠的组件,这些组件可以在其下方显示子组件,但是您可以使用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 您将BufferedImage定义为height width ,然后使用full_widthfull_height填充它...在我看来,以另一种方式做起来更有意义

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

Okay, important lesson in Swing, you don't control the paint process, so don't try. 好的,这是Swing中的重要课程,您无法控制绘画过程,因此请勿尝试。 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 ). Swing具有完善的记录和建立良好的绘画过程,可提供定义明确的挂钩,您可以在其中插入自定义绘画(即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. 如果需要“控制”,则需要考虑使用BufferStrategy ,它将为您提供完全的控制以定义您自己的绘画过程。

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. 嗯,这不是那么简单,因为我不确定100%知道我要解决的问题是什么。

But, let's start with the Dr panel... 但是,让我们从Dr面板开始...

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. 因此,在这里,我对其进行了更改,以便将面板定位在传递给构造函数的xy位置,并将其大小设置为fwfh属性。

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... 然后在draw方法中,我创建一个BufferedImage大小,该大小调整为组件的当前大小并进行绘制...基于widthheight属性的内容...提出了有关为什么必须调整大小的问题,但是在那里是...

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

ps- The red rectangle is for debugging purposes, you don't need it. ps-红色矩形用于调试目的,您不需要它。

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 而且,我的直觉是,您可能真的不想使用单独的组件,而您想要的是一个可以绘制许多缓冲区的组件

I finally solved the issue. 我终于解决了这个问题。

The problem was in the code that I didn't post here. 问题出在我没有在此处发布的代码中。

I added my Dr JPanels into Block JPanel. 我将我的JPanels Dr添加到Block JPanel中。 Block had repaint method overridden to draw from the buffer, but hadn't overridden paintComponent method in current version. Block已重写repaint方法以从缓冲区进行绘制,但在当前版本中未重写paintComponent方法。 It also had method forceRepaint to rebuild the buffer, and methods draw(Graphics g) and draw() to draw on the internal buffer or on the passed Graphics context, respectively. 它还具有方法forceRepaint来重建缓冲区,而方法draw(Graphics g)draw()分别在内部缓冲区或传递的Graphics上下文上进行绘制。 paint method wasn't overridden either. paint方法也没有被覆盖。

As a result, after I added my children with call to add , Swing called the repaint on my Block object. 结果,在我给孩子add调用之后,Swing在我的Block对象上调用了repaint But my version of repaint simply updated the internal buffer, and that was all. 但是我的repaint版本只是更新了内部缓冲区,仅此repaint The result on the screen didn't change in any way. 屏幕上的结果没有任何改变。

So that was just a logical error. 因此,这只是一个逻辑错误。

The solution was to re-implement the overridden version of paintComponent in Block class, a class whose instance is the root object where I add elements. 解决方案是在Block类中重新实现paintComponent的重写版本,该类的实例是我在其中添加元素的root对象。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM