1

It shows the line without jpanel on jframe, but it doesn't when I add it to jpanel. I've tried setting the layout manager of jpanel to null but no result. I want to use JComponents for drawing lines because I want them clickable.

Main.java file:

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.geom.Line2D;
import java.awt.geom.Point2D;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JPanel;

class Main {
  public static void main(String[] args) {
    JFrame frame = new JFrame();
    frame.setDefaultCloseOperation(frame.EXIT_ON_CLOSE);
    frame.setSize(500, 500);

    //Parent Panel
    JPanel panel = new JPanel();
    panel.setBackground(Color.YELLOW);
    panel.setLayout(null);

    //Add Line To Panel
    Line line = new Line(new Point2D.Double(20,20), new Point2D.Double(180,180));

    panel.add(line);
    panel.repaint();

    frame.add(panel);
    frame.setVisible(true);
  }
}

class Line extends JComponent {

   private final Point2D start, end;

    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g);
        Graphics2D g2 = (Graphics2D) g;
        g2.setColor(Color.BLUE);
        g2.setStroke(new BasicStroke(2.0F));
        g2.draw(new Line2D.Double(start,end));
    }

    public Line( Point2D start, Point2D end){
        this.start = start;
        this.end = end;
        addMouseListener(new MouseAdapter() {
            @Override
            public void mouseClicked(MouseEvent e) {
                System.out.println("mouse clicked");
            }
        });
    }
}
meliddu
  • 23
  • 5

2 Answers2

1

It shows the line without jpanel on jframe, but it doesn't when I add it to jpanel

Swing components are responsible for determining their own preferred size.

When you add a component to a panel, the layout manager will then set the size/location of the component based on the rules of the layout manager.

When you add a component to the frame you really add it to the content pane of the frame which is a Jpanel which uses a BorderLayout by default. So the component is sized to fill the space available in the frame.

panel.setLayout(null);

You then added the component to a panel with a null layout. Now you are responsible for setting the size/location of the component. If you don't the size is (0, 0) so there is nothing to paint.

You should override the getPreferredSize() method of your class to return the preferred size of the component. Then layout managers can do their job.

If you really need a null layout, then the size of the component should be set in the application code, not it the Line class itself.

But now my line has a big container that listens for any clicks,

If you want hit detection then you override the contains(...) method.

Here is a basic example implementing the above suggestions:

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.geom.Line2D;
import java.awt.geom.Point2D;
import java.awt.Rectangle;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JPanel;

public class Line extends JComponent
{
   private Line2D.Double line;

    public Line( Point2D start, Point2D end)
    {
        line = new Line2D.Double(start, end);

        addMouseListener(new MouseAdapter()
        {
            @Override
            public void mouseClicked(MouseEvent e)
            {
                System.out.println("mouse clicked");
            }
        });
    }

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

        Graphics2D g2 = (Graphics2D) g;
        g2.setColor( Color.BLUE );
        g2.setStroke( new BasicStroke(2.0F) );
        g2.draw( line );
    }

    @Override
    public Dimension getPreferredSize()
    {
        Rectangle bounds = line.getBounds();

        int width = bounds.x + bounds.width;
        int height = bounds.y + bounds.height;

        return new Dimension(width, height);
    }

    @Override
    public boolean contains(int x, int y)
    {
        double distance = line.ptSegDist( new Point2D.Double(x, y) );

        return distance < 2;
    }

    public static void main(String[] args)
    {
        JFrame frame = new JFrame();
        frame.setDefaultCloseOperation(frame.EXIT_ON_CLOSE);
        frame.setSize(500, 500);

        //Parent Panel
        JPanel panel = new JPanel();
        panel.setBackground(Color.YELLOW);

        //Add Line To Panel
        Line line = new Line(new Point2D.Double(20,20), new Point2D.Double(180,180));

        panel.add(line);
        panel.repaint();

        frame.add(panel);
        frame.setVisible(true);
    }
}
camickr
  • 321,443
  • 19
  • 166
  • 288
  • This helped a lot. So the `contains()` method the thing I was looking for. Component's size is still big though but it wouldn't matter right? – meliddu Jun 02 '20 at 06:37
  • "_If you really need a null layout, then the size of the component should be set in the application code, not it the Line class itself._" May I also ask how to do that? – meliddu Jun 02 '20 at 07:29
  • In your application code when you create the Line instance you would invoke `line.setSize( line.getPreferredSize() )` – camickr Jun 02 '20 at 13:56
-1

Add custom size in Line constructor.

public Line( Point2D start, Point2D end){ ...
this.setSize(200, 200); }

enter image description here

Updated to fit also with painted Graph

Advice to change from JComponent to JPanel in order to see background

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.geom.Line2D;
import java.awt.geom.Point2D;
import javax.swing.JFrame;
import javax.swing.JPanel;

class Main {
  public static void main(String[] args) {
    JFrame frame = new JFrame();
    frame.setDefaultCloseOperation(frame.EXIT_ON_CLOSE);
    frame.setSize(500, 500);

    //Parent Panel
    JPanel panel = new JPanel();
    panel.setSize(300,300);
    frame.add(panel);
    panel.setBackground(Color.YELLOW);
    panel.setLayout(null);

    //Add Line To Panel
    Line line = new Line(new Point2D.Double(20,20), new Point2D.Double(180,180));
    panel.add(line);

    frame.setVisible(true);
  }
}

class Line extends JPanel {

   private final Point2D start, end;

    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g);
        Graphics2D g2 = (Graphics2D) g;
        g2.setBackground(Color.RED);
        g2.setColor(Color.BLUE);
        g2.setStroke(new BasicStroke(2.0F));
        g2.draw(new Line2D.Double(start,end));
        Rectangle r = g2.getClipBounds();
        System.out.println(r.x+":"+r.y);
    }

    public Line( Point2D start, Point2D end){

        this.start = start;
        this.end = end;
        addMouseListener(new MouseAdapter() {
            @Override
            public void mouseClicked(MouseEvent e) {
                System.out.println("mouse clicked at "+e.getX()+":"+e.getY());

            }
        });
        int max_x = (int) Math.max(start.getX(), end.getX());
        int max_y = (int) Math.max(start.getY(), end.getY());
        System.out.println("max x="+max_y+",y="+max_y);
        setSize(max_x,max_y);
        setVisible(true);
        setBackground(Color.GREEN);
    } 
}

enter image description here

Note: Only inside_green clicks allowed !

Traian GEICU
  • 1,750
  • 3
  • 14
  • 26
  • Thanks, this solves the visibility problem. But now my line has a big container that listens for any clicks, that's going to cause mess when there are multiple lines on panel. Is there way to set the size as big as line? Or Can I make it auto generate dimensions by line's position and length? – meliddu Jun 01 '20 at 10:44
  • `size` is whatever wanted to be set, use constructor args for pre-calculated size. Initial question was regarding visibility ... and that's is fixed. – Traian GEICU Jun 01 '20 at 10:53
  • eg: last_line(180,180) so size should be 180x180 ... etc (check max points and do size with then) – Traian GEICU Jun 01 '20 at 10:55
  • Is there another way to set size of the component same as the painting? Because in that way I'll have to set the orientation of container too. I've seen a method of Line2D.Double, getBounds2D() which returns a Rectangle2D object that closes the area of shape as it says. Is there a way to use it? – meliddu Jun 01 '20 at 11:01
  • Not Sure(but assume yes, should go further with docs). But you always should know "point_coords" and could calculate size based on max point coords (eg : `this.setSize( (int)end.getX(), (int) end.getY())) `... `getBounds` - could be used also – Traian GEICU Jun 01 '20 at 11:08
  • Alright. I'll keep this thread not answered to see if anyone has a better idea about adjusting sizes. I'll choose this answer again as correct one later. Thanks for help. – meliddu Jun 01 '20 at 11:15
  • up to you ... but also is better to modify the question and title to fit the new requirements ... – Traian GEICU Jun 01 '20 at 11:25
  • 1
    The component should NOT set the size() of itself. The component should only determines its preferred size. The layout manager will then use this information to set the size/location of the component. If a null layout is used then the application will determine the size/location of the component. – camickr Jun 01 '20 at 15:23
  • @camickr "The component should NOT set the size() of itself" why not ? is a solution ! (maybe there are others, fine ... ) – Traian GEICU Jun 01 '20 at 19:01
  • @TraianGEICU I agree with camickr. See https://stackoverflow.com/q/9536804/1288408 – Modus Tollens Jun 01 '20 at 19:07
  • @ModusTollens, I said it's a solution ! ... that's all ... if there are others, or any other suggestions are better, that's another story ... – Traian GEICU Jun 01 '20 at 19:20
  • 1
    @TraianGEICU I understand. Still, we were pointing out a mistake. Since this code is supposed to help many users, pointing out mistakes in code is important. – Modus Tollens Jun 01 '20 at 19:28
  • @ModusTollens , mistake to `setSize` on Component NO, but depends of what's next intention maybe not recommended to do as I suggested in first post.(was something just for this question ...). Agree that "code is supposed to help many users" and any fine with any improvements ! but just replay of what wasn't fine (NO mistake in code provided but for a specific usage better to see all posts) – Traian GEICU Jun 01 '20 at 19:35
  • @ModusTollens stackoverflow.com/q/9536804/1288408 , fine also but in this example out of questions ... – Traian GEICU Jun 01 '20 at 19:38
  • 1
    @TraianGEICU I am sorry, not to be mean, but your code has another problem: It calls non-final methods in constructors (e.g. setSize in Line). This is a massive red flag for obscure possible problems that can pop up when you run it. https://docs.oracle.com/javase/tutorial/java/IandI/final.html – Modus Tollens Jun 01 '20 at 19:39
  • 1
    @ Modus Tollens , Fine with notice, good to point at ! ... on my run was fine ... but good to know ! – Traian GEICU Jun 01 '20 at 19:41
  • 2
    Of course there is always more than one approach to solve a problem However, as ModusTollens has pointed out the answers of this forum are meant to encourage best practices. Best practices exist for a reason. They may not be apparent now but they will prevent potential problems in the future. – camickr Jun 02 '20 at 00:07