0

I'm trying to make a border with round corners. Inside the borders should be anything that the component, for which the border is set, decides to draw, and outside the borders should "nothing"; that is, it should draw the parent component's paint in those places.

What I'm trying to get:

enter image description here

What I'm getting:

enter image description here

See the white corners of the container with blue borders. I need to get rid of them. I'm trying to achieve this with a custom Border :

public class RoundedLineBorder extends AbstractBorder {

    @Override
    public void paintBorder(Component c, Graphics g, int x, int y, int width, int height) {
        Graphics2D g2 = (Graphics2D) g;            
        int arc = 20;

        RoundRectangle2D borderRect = new RoundRectangle2D.Double(
                0, 0, width - 1, height - 1, arc, arc);
        Rectangle fullRect = new Rectangle(
                0, 0, width, height);

        Area borderArea = new Area(borderRect);
        Area parentArea = new Area(fullRect);
        parentArea.subtract(borderArea);

        Component parent = c.getParent();
        if (parent != null) {
            g2.setColor(parent.getBackground());

            /* fill parent background color outside borders */
            g2.setClip(parentArea);
            g2.fillRect(fullRect);

            g2.setClip(null);
        }

        g2.setColor(Color.blue);

        /* draw borders */
        g2.draw(borderArea);
    }

}

This works fine when the parent component has a solid background, but if it has a background image it of course doesn't. Is there a way of getting the actual colours that are painted under the aforementioned places?

Is there a better way of achieving rounded borders without actually extending a JPanel and just doing it all in its paintComponent?

Olavi Mustanoja
  • 2,045
  • 2
  • 23
  • 34
  • A "possible" [example](http://stackoverflow.com/questions/23945781/is-there-a-way-to-make-jtextfield-for-my-address-bar-larger-and-curvier/23945841#23945841) – MadProgrammer Jan 09 '15 at 22:53
  • @MadProgrammer a possible example, yes. I wonder if it's doable with borders, though. – Olavi Mustanoja Jan 09 '15 at 23:04
  • @MadProgrammer I'm preferring borders since I can't, for example, do that if the component that should receive the border also has a non-solid background. – Olavi Mustanoja Jan 09 '15 at 23:15
  • The problem is, the component has no idea what shape the border is, therefore it doesn't know how it should fill itself, you really don't want this any way, as it requires a tight coupling between the border and the component, meaning that every component you applied the border to would need to be customized to handle it. Borders themselves don't fill, as they are painted after paintComponent is called ... – MadProgrammer Jan 09 '15 at 23:24
  • I'd also prefer if borders could provide more information to the component as well. But string components tend to be either opaque or transparent, not a mixture of both, which is what your after (it's doable, just not supported by default) – MadProgrammer Jan 09 '15 at 23:26

1 Answers1

1

it requires a tight coupling between the border and the component,

You could create a container component to do this so you don't need to customize every component.

Something like:

import java.awt.*;
import java.awt.geom.*;
import javax.swing.*;
import javax.swing.border.*;

public class RoundedBorderContainer extends JPanel implements Border
{
    private boolean componentWasOpaque;

    public RoundedBorderContainer(JComponent component)
    {
        setLayout( new BorderLayout() );
        add( component );

        componentWasOpaque = component.isOpaque();
        component.setOpaque( false );
        setOpaque( false );

        setBorder( this );
    }

    @Override
    public void paint(Graphics g)
    {
        Graphics2D g2 = (Graphics2D) g;
        int arc = 20;
        int width = getWidth();
        int height = getHeight();

        RoundRectangle2D borderRect = new RoundRectangle2D.Double(0, 0, width, height, arc, arc);
        g2.setClip(borderRect);

        super.paint(g);
        super.paintBorder(g);

        g2.setClip( null );
    }

    @Override
    protected void paintComponent(Graphics g)
    {
        super.paintComponent(g);

        if (componentWasOpaque)
        {
            g.setColor(getComponent(0).getBackground());
            g.fillRect(0, 0, getWidth(), getHeight());
        }
    }

    @Override
    public Insets getBorderInsets(Component c)
    {
        return new Insets(1, 1, 1, 1);
    }

    @Override
    public boolean isBorderOpaque()
    {
        return false;
    }

    @Override
    public void paintBorder(Component c, Graphics g, int x, int y, int width, int height)
    {
        Graphics2D g2 = (Graphics2D) g;

        int arc = 20;
        RoundRectangle2D borderRect = new RoundRectangle2D.Double(0, 0, width - 1, height - 1, arc, arc);
        Rectangle fullRect = new Rectangle(0, 0, width, height);

        Area borderArea = new Area(borderRect);
        borderArea = new Area(borderRect);
        Area parentArea = new Area(fullRect);

        g2.setColor(Color.RED);

        g2.draw(borderArea);
    }

    private static void createAndShowGUI()
    {
        JLabel label = new JLabel( new ImageIcon("grass.jpg") );
        label.setLayout( new GridBagLayout() );

        JPanel panel = new JPanel();
        panel.setPreferredSize( new Dimension(100, 100) );
        panel.setBackground( Color.BLACK );

//        JLabel panel = new JLabel( new ImageIcon("???") );

        RoundedBorderContainer rbc = new RoundedBorderContainer(panel);
        label.add(rbc);

        JFrame frame = new JFrame("Rounded Border");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.add( label );
        frame.setLocationByPlatform( true );
//        frame.pack();
        frame.setSize(400, 400);
        frame.setVisible( true );
    }

    public static void main(String[] args)
    {
        EventQueue.invokeLater(new Runnable()
        {
            public void run()
            {
                createAndShowGUI();
            }
        });
    }
}

Rounded Border

camickr
  • 321,443
  • 19
  • 166
  • 288
  • This is another quick solution, but with this I can't define another type of border, or have a non-solid background on the panel without completely creating a new class from scratch for each case. – Olavi Mustanoja Jan 10 '15 at 02:27
  • Any time you want a new Border you need to create a new class and implement the Border interface. Yes you have an extra step to manage the clipping of the Border. `or have a non-solid background on the panel` - I assume you mean an image? This does present a problem as child components are painted after the Border and the component is painting over part of the Border. I added a hack to repaint the Border after painting the child component. This is the best I can do. – camickr Jan 10 '15 at 03:52