Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
475 views
in Technique[技术] by (71.8m points)

java - Can't paint a custom component in Swing correctly

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 wanted

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

direct paint

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)

direct paint, no background fill

What is displayed when I drraw from my buffer

enter image description here

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).

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

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...

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


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...