首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >无法在Swing中正确绘制自定义组件

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

Stack Overflow用户
提问于 2018-03-24 10:34:04
回答 2查看 431关注 0票数 1

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

我的代码的错误之处在于它根本不能在重叠的JPanels上绘制数据。我已经为问题的核心做了一个可重复的例子。请参见以下代码:

代码语言:javascript
复制
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%不透明度的一半的红色矩形。

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

代码语言:javascript
复制
    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();

预期为:

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

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

当我从我的缓冲区中提取原始数据时会显示什么

最后一个是非常有趣的。红色矩形从右侧和底部部分剪切,完整图像(包括白色背景)从父面板的(0,0)坐标裁剪。第二个对象根本不显示。控制台中的坐标是绝对正确的。

我觉得我必须对Swing做一些事情,告诉它不要在猜测什么应该画什么不应该画什么的时候表演它的“魔力”。但是我该怎么做呢?

更新:

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

EN

回答 2

Stack Overflow用户

发布于 2018-03-24 12:45:05

所以,我“认为”你有一个根本的误解,好吧,一切都在Swing中工作。

您似乎对布局管理系统的工作方式、绘画系统的工作方式以及坐标系的工作方式存在问题

让我们从Dr组件开始。

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

这..。

代码语言:javascript
复制
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);
}

在我看来很奇怪。您将BufferedImage定义为由height调整为width的大小,但是在我看来,使用full_widthfull_height填充it...it似乎更有意义

代码语言:javascript
复制
@Override
public void repaint() {
    draw();
}

好的,Swing的重要一课,你不能控制绘画过程,所以不要尝试。Swing有一个文档齐全、建立良好的绘制过程,它提供了定义良好的钩子,您可以将自定义的绘制(即paintComponent)注入其中。如果你需要“控制”,那么你需要考虑使用BufferStrategy,它将给你完全的控制来定义你自己的绘制过程。

好的,那么答案是什么?

好吧,这并不是那么直接,因为我不是百分之百地肯定我理解你试图解决的问题是什么。

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

代码语言:javascript
复制
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位置,并根据fwfh属性调整大小。

draw方法中,我创建了一个根据组件当前大小调整大小的BufferedImage,并绘制...不管怎样..。基于widthheight属性...关于我们为什么要调整尺寸的问题被提出了,但是,这就是问题所在。

然后,缓冲区被绘制到组件的左上角位置,这一点很重要,Swing的坐标系是基于组件本身的,所以0x0始终是组件的左上角,坐标系与父容器无关。

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

然后我使用...

代码语言:javascript
复制
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);
    }
});

瞧,重叠的组件。

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

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

票数 2
EN

Stack Overflow用户

发布于 2018-03-24 15:25:11

我终于解决了这个问题。

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

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

因此,在我通过调用add添加了我的孩子之后,Swing在我的Block对象上调用了repaint。但我的repaint版本只是更新了内部缓冲区,仅此而已。屏幕上的结果没有以任何方式改变。

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

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

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/49460644

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档