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

时间:2018-03-24 02:34:04

标签: java swing

我知道Swing很差,很抱歉这个愚蠢的问题。 我需要做的是使用缓冲区(JPanel实例)执行绘制我的自定义组件(它们是BufferedImage的祖先)。这是一个要求,因为绘制过程可能非常繁重,所以paintComponent方法被覆盖以从该缓冲区中绘制并立即返回(但是如果你知道更好的方法告诉Java不要再重复该对象超过200-300次消耗所有的CPU - 我很欣赏,也许有一些公共方法将Graphics上下文区域标记为“未更改”,因此Swing不会尝试重新绘制它们。)

我的代码有什么问题,它根本不会在重叠的JPanel上绘制数据。我已经提出了问题核心的可重现的例子。请参阅以下代码:

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

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

以下是使用它的外部代码:

    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 wanted

当我删除缓冲区并直接使用paintComponent方法绘制时显示的内容:

direct paint

当我不填充背景时显示的内容(此处背景有系统L& F颜色,但我需要元素父级的颜色(在我的示例中为白色)

direct paint, no background fill

从我的缓冲区中删除时显示的内容

enter image description here

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

我觉得我有一些与Swing有关的事情要告诉它在猜测画什么和不画什么时不要表现出它的“魔力”。但是我该怎么做呢?

更新:

我发现,即使我在添加它们之后没有调用我的子组件的draw()方法,它们仍然隐藏了它们的父级背景(这不应该发生,因为它不应该是不透明的)。所以核心问题是我不能让我的Dr对象具有不透明背景(例如没有背景),其中没有任何东西被绘制(这也解释了为什么我只看到第一个矩形 - 在Swing中的z顺序似乎是相反的,例如最后添加的组件是在底部绘制的,因为它距离我们最远。)

2 个答案:

答案 0 :(得分:2)

所以,我认为"你有一个根本的误解是,一切都在Swing中有效。

你"似乎"关于布局管理系统如何工作,绘画系统如何工作以及坐标系如何工作的问题

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

您似乎想要开发重叠组件,这些组件可以显示其下方的子组件,但您使用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);
}

对我来说似乎很奇怪。您可以BufferedImagewidth的大小定义为height,然后使用full_widthfull_height来填充它......对我来说,似乎更有意义的是反过来做

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

好的,Swing的重要课程,你不能控制油漆过程,所以不要试试。 Swing有一个记录良好且完善的绘画过程,可提供定义明确的钩子,您可以在其中注入自定义绘画(即paintComponent)。如果您需要"控制",那么您需要考虑使用BufferStrategy,这将为您提供完全控制以定义您自己的绘画过程。

好的,那么答案是什么?

嗯,那不是那么直截了当,因为我并不是100%确定我明白你要解决的问题是什么。

但是,让我们从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;
}

所以,在这里,我已经对其进行了更改,以便将面板定位在您传递给构造函数的xy位置,并将其大小调整为{{1} }和fw属性。

fh方法中,我创建一个draw大小的组件当前大小并根据BufferedImage和...绘制... ... width属性......提出了关于我们为什么要调整大小的问题,但是,它就是......

然后缓冲区被绘制到组件的顶部/左侧位置,这很重要,Swing的坐标系统基于组件本身,因此height始终是顶部/左侧角该组件,坐标系与父容器无关。

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

然后我用...

0x0

瞧,重叠组件......

Overlapping components

现在,我强烈建议您停下来阅读:

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

答案 1 :(得分:0)

我终于解决了这个问题。

问题在于我没有在这里发布的代码。

我将Dr JPanel添加到Block JPanel中。 Block已覆盖repaint方法从缓冲区中绘制,但在当前版本中没有覆盖paintComponent方法。它还有方法forceRepaint来重建缓冲区,方法draw(Graphics g)draw()分别在内部缓冲区或传递的Graphics上下文上绘制。 paint方法也未被覆盖。

因此,在我将孩子的电话加入add后,Swing在我的repaint对象上调用了Block。但我的repaint版本只是更新了内部缓冲区,就是这样。屏幕上的结果没有任何改变。

所以这只是一个逻辑错误。

解决方案是在paintComponent类中重新实现Block的重写版本,该类的实例是我添加元素的root对象。