Jeffrey Pham
Jeffrey Pham

Reputation: 1

How do I make the part that I'm not using of a simple JComponent transparent, so that when add it to a JFrame it does not cover other components?

I'm having problems adding 2 simple components to a JFrame. The problem is, the second component completely covers the first component even though each only contains a small shape located differently on the JFrame. I've browse for a while but cannot find a solution to my problem.

public class Main
{
    public static void main(String[]args)
    {
        JFrame frame = new JFrame();
        CircleComponent comp = new CircleComponent(400,400);
        CircleComponent comp2 = new CircleComponent(200,200);

        frame.add(comp);
        frame.add(comp2);
        frame.setSize(800,800);
        frame.setVisible(true);
    }
}

//////////////////////////////////////////////////////////////////////////

public class CircleComponent extends JComponent
{
    Ellipse2D.Double ellipse;
    double x; 
    double y;
    public CircleComponent(int xx, int yy)
   {
        x = xx;
        y = yy;
        ellipse = new Ellipse2D.Double(x,y,25,25);
        setOpaque(false);
    }

   public void paintComponent(Graphics g)
    {
        super.paintComponent(g);
        Graphics2D g2 = (Graphics2D) g;
        g2.fill(ellipse);
    }
}

Upvotes: 0

Views: 61

Answers (1)

camickr
camickr

Reputation: 324118

This has nothing to do with visibility. It has to do with how layout managers work.

The default layout manager for a JFrame is a BorderLayout. When you add components to the frame without specifying a constraint the component get added to the CENTER. However only one component can ever be added to the CENTER, so only the last component added gets painted.

Your component code is incorrect. In order for a component to be painted it needs to have a preferred size and a size. The layout manager will determine the size by using the rules of the layout manager and its preferred size.

Your code happens to work because the rules for the CENTER of the BorderLayout is to give the component all the space available. In your case 800x800 (less the borders of the frame).

Edit:

Should I add the components to a JPanel with a layoutmanager that allows them to overlap each other?

You would still need to implement the getPreferredSize(..) method. The preferred size would need to take into account the x/y position as well as the width/height of the rectangle. Once this is done you might be able to use a layout manager. However, there are no standard layout managers that provide this support.

However, you can use the Overlap Layout.

Another approach is to have your component always do the painting of the Rectangle at offset (0, 0) of the component. Then you would position the component by using the setLocation(...) method of the component. This is the more general approach of how component are used in Swing. I happen to have a class I was playing with a couple of days ago that does this.

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Insets;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.Shape;
import javax.swing.JComponent;

/**
 *  A component that will paint a Shape object. Click detection will be
 *  determined by the Shape itself, not the bounding Rectangle of the Shape.
 *
 *  Shape objects can be created with an X/Y offset. These offsets will
 *  be ignored and the Shape will always be painted at (0, 0) so the Shape is
 *  fully contained within the component.
 *
 *  The foreground color will be used to "fill" the Shape.
 */
public class ShapeComponent extends JComponent
{
    private Shape shape;
    private boolean antiAliasing = true;

    /**
     *  Create a ShapeComponent that is painted black.
     *
     *  @param shape the Shape to be painted
     */
    public ShapeComponent(Shape shape)
    {
        this(shape, Color.BLACK);
    }

    /**
     *  Create a ShapeComponent that is painted filled and outlined.
     *
     *  @param shape the Shape to be painted
     *  @param color the color of the Shape
     */
    public ShapeComponent(Shape shape, Color color)
    {
        setShape( shape );
        setForeground( color );

        setOpaque( false );
    }

    /**
     *  Get the Shape of the component
     *
     *  @returns the the Shape of the compnent
     */
    public Shape getShape()
    {
        return shape;
    }

    /**
     *  Set the Shape for this component
     *
     *  @param shape the Shape of the component
     */
    public void setShape(Shape shape)
    {
        this.shape = shape;
        revalidate();
        repaint();
    }

    /**
     *  Use AntiAliasing when painting the shape
     *
     *  @returns true for AntiAliasing false otherwise
     */
    public boolean isAntiAliasing()
    {
        return antiAliasing;
    }

    /**
     *  Set AntiAliasing property for painting the Shape
     *
     *  @param antiAliasing true for AntiAliasing, false otherwise
     */
    public void setAntiAliasing(boolean antiAliasing)
    {
        this.antiAliasing = antiAliasing;
        revalidate();
        repaint();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Dimension getPreferredSize()
    {
        //  Include Border insets and Shape bounds

        Insets insets = getInsets();
        Rectangle bounds = shape.getBounds();

        //  Determine the preferred size

        int width = insets.left + insets.right + bounds.width;
        int height = insets.top + insets.bottom + bounds.height;

        return new Dimension(width, height);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Dimension getMinimumSize()
    {
        return getPreferredSize();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Dimension getMaximumSize()
    {
        return getPreferredSize();
    }

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

        //  Graphics2D is required for antialiasing and painting Shapes

        Graphics2D g2d = (Graphics2D)g.create();

        if (isAntiAliasing())
            g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);

        //  Shape translation (ie. non-zero X/Y position in bounding rectangle)
        //  and Border insets.

        Rectangle bounds = shape.getBounds();
        Insets insets = getInsets();

        //  Do all translations at once

        g2d.translate(insets.left - bounds.x, insets.top - bounds.y);

        //  Fill the Shape

        g2d.fill( shape );

        g2d.dispose();
    }

    /**
     *  Determine if the point is in the bounds of the Shape
     *
     * {@inheritDoc}
     */
    @Override
    public boolean contains(int x, int y)
    {
        Rectangle bounds = shape.getBounds();
        Insets insets = getInsets();

        //  Check to see if the Shape contains the point. Take into account
        //  the Shape X/Y coordinates, Border insets and Shape translation.

        int translateX = x + bounds.x - insets.left;
        int translateY = y + bounds.y - insets.top;

        return shape.contains(translateX, translateY);
    }
}

This code is more flexible than your component since you can create a circle, rectangle or any other shape. The basic code would be:

Shape circle = new Ellipse2D.Double(0, 0, 25, 25);
ShapeComponent sc = new ShapeComponent(circle, Color.RED);
circle.setLocation(400, 400);

You would also want to use a special layout as well. So check out the Drag Layout which can be used for random location of components.

Upvotes: 2

Related Questions