paintComponent() is drawing on other components

2019-04-09 22:09发布

I'm using a custom class, based on the code in this answer, to draw a background shaped like a speech bubble. Whenever I resize the window of my application enough to make a component poke out at the top or bottom, the outlines of the said component is drawn outside the JScrollPane on top of other components; in this case the JPanel.

In the left-side image, the border of the component at the bottom of the JScrollPane is drawn, due to the component still being visible; while in the right-side image, the mentioned component is no longer visible and everything looks as intended.

I believe it has something to do with the fact that I'm using a JScrollPane to contain the components and thus allowing the component to slide under the JPanel. How do I prevent this?

Image

Main:

public class Main {
    public static void main(String[] args) {
        JPanel panel = new JPanel(), panelbar = new JPanel();
        panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
        panelbar.setLayout(new FlowLayout());

        JScrollPane scroll = new JScrollPane(panel,
                JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,
                JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);

        JFrame frame = new JFrame("");
        frame.setLayout(new BorderLayout());
        frame.setSize(200, 223);

        for (int i = 0; i < 6; i++) {
            JLabel label = new JLabel("JLabel");
            label.setBorder(new CustomBorder());
            label.setOpaque(true);
            label.setBackground(Color.ORANGE);
            panel.add(label);
        }

        panelbar.add(new JLabel("JPanel"));

        frame.add(scroll, BorderLayout.CENTER);
        frame.add(panelbar, BorderLayout.SOUTH);

        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setVisible(true);
    }
}

Custom class:

public class CustomBorder extends AbstractBorder {
    private static final long serialVersionUID = 1L;
    Insets i;

    CustomBorder() {
        i = new Insets(10, 20, 10, 20);
    }

    @Override
    public void paintBorder(Component c, Graphics g, int x, int y, int width, int height) {
        super.paintBorder(c, g, x, y, width, height);

        Polygon bubble = new Polygon();
        bubble.addPoint(x + 10, y + 5);
        bubble.addPoint(x + width - 10, y + 5);
        bubble.addPoint(x + width - 10, y + height / 3);
        bubble.addPoint(x + width, y + height / 2);
        bubble.addPoint(x + width - 10, y + height * 2 / 3);
        bubble.addPoint(x + width - 10, y - 5 + height);
        bubble.addPoint(x + 10, y - 5 + height);

        Graphics2D g2d = (Graphics2D) g;
        Area rect = new Area(new Rectangle(x, y, width, height));
        rect.subtract(new Area(bubble));
        g2d.setClip(rect);
        g2d.setColor(c.getParent().getBackground());
        g2d.fillRect(0, 0, width, height);
        g2d.setClip(null);
        g2d.setColor(Color.BLACK);
        g2d.draw(bubble);
    }

    @Override
    public Insets getBorderInsets(Component c) {
        return i;
    }

    @Override
    public Insets getBorderInsets(Component c, Insets insets) {
        return i;
    }
}

2条回答
该账号已被封号
2楼-- · 2019-04-09 22:37

Your basic problem is, you're changing the clipping area, which was set before the component was painted, to something, well, else, which is allowing you to paint beyond the bounds of the component...

As discussed here and here, borders aren't meant to be filled, nor do they effect the area filled by paintComponent

If you take a look at A Closer Look at the Paint Mechanism you will see the paintComponent is called before paintBorder...

javax.swing.JComponent extends this class and further factors the paint method into three separate methods, which are invoked in the following order:

  • protected void paintComponent(Graphics g)
  • protected void paintBorder(Graphics g)
  • protected void paintChildren(Graphics g)

So, what's the solution? Fake it!

Fake it

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.EventQueue;
import java.awt.FlowLayout;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.Polygon;
import javax.swing.BoxLayout;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.border.EmptyBorder;

public class BorderCheat {

    public static void main(String[] args) {
        new BorderCheat();
    }

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

                JPanel panel = new JPanel(), panelbar = new JPanel();
                panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
                panelbar.setLayout(new FlowLayout());

                JScrollPane scroll = new JScrollPane(panel,
                        JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,
                        JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);

                for (int i = 0; i < 6; i++) {
                    BubblePane bp = new BubblePane();
                    bp.setBackground(Color.ORANGE);
                    JLabel label = new JLabel("JLabel");
                    bp.add(label);
                    panel.add(bp);
                }

                panelbar.add(new JLabel("JPanel"));

                JFrame frame = new JFrame("Testing");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.add(scroll);
                frame.add(panelbar, BorderLayout.SOUTH);
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public class BubblePane extends JPanel {

        public BubblePane() {
            setLayout(new GridBagLayout());
            setBorder(new EmptyBorder(10, 20, 10, 30));
            setOpaque(false);
        }

        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            Graphics2D g2d = (Graphics2D) g.create();
            Insets insets = getInsets();
            int x = 0;
            int y = 0;
            int width = getWidth();
            int height = getHeight();
            Polygon bubble = new Polygon();
            bubble.addPoint(x, y);
            bubble.addPoint(x + width - insets.right + 10, y);
            bubble.addPoint(x + width - insets.right + 10, y + height / 3);
            bubble.addPoint(x + width, y + height / 2);
            bubble.addPoint(x + width - insets.right + 10, y + height * 2 / 3);
            bubble.addPoint(x + width - insets.right + 10, y + height);
            bubble.addPoint(x, y + height);

            g2d.setColor(getBackground());
            g2d.fill(bubble);
            g2d.setColor(Color.BLACK);
            g2d.draw(bubble);
            g2d.dispose();
        }

    }

}

Okay, "but there's no gap between them" you say. Okay, so use a CompoundBorder or a layout which allows you to specify the vertical or horizontal spacing between components...

查看更多
贼婆χ
3楼-- · 2019-04-09 22:41

There are two problems with the clipping code:

  1. You don't start with the original clip when subtracting out the bubble (causing the component to be painted outside the scrollpane)
  2. You don't restore the original clip before painting the bubble:

The changes would be:

@Override
public void paintBorder(Component c, Graphics g, int x, int y, int width, int height) {
    super.paintBorder(c, g, x, y, width, height);

    Polygon bubble = new Polygon();
    bubble.addPoint(x + 10, y + 5);
    bubble.addPoint(x + width - 10, y + 5);
    bubble.addPoint(x + width - 10, y + height / 3);
    bubble.addPoint(x + width, y + height / 2);
    bubble.addPoint(x + width - 10, y + height * 2 / 3);
    bubble.addPoint(x + width - 10, y - 5 + height);
    bubble.addPoint(x + 10, y - 5 + height);

    Graphics2D g2d = (Graphics2D) g;
    //Area rect = new Area(new Rectangle(x, y, width, height));
    Shape clip = g2d.getClip();
    Area rect = new Area(clip);
    rect.subtract(new Area(bubble));
    g2d.setClip(rect);
    g2d.setColor(c.getParent().getBackground());
    g2d.fillRect(0, 0, width, height);
    //g2d.setClip(null);
    g2d.setClip(clip);
    g2d.setColor(Color.BLACK);
    g2d.draw(bubble);
}
查看更多
登录 后发表回答