javaswingcomponentsjpaneljcomponent

How to get Java Swing Components to display on a JPanel within a JFrame, so you can add and remove them afterwards?


I have this custom component:

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

public class Comp extends JComponent{
    
        int x;
        int y;
        String name;
    
        public Comp(int px, int py, String pn){
            name=pn;
            System.out.println("new cp--"+name);
            x = px;
            y = py;
            //setBounds(x, y, 50, 50);
        }
        @Override
        protected void paintComponent(Graphics g){
            System.out.println("paint cp--"+name);
            g.setColor(Color.BLACK);
            g.drawOval(x,y,50,50);
            g.drawString(name, x+25, y+25);
        }

        @Override
        public Dimension getPreferredSize() {
            System.out.println("\tG pref size");
            return new Dimension(50,50);
        }
    }

and this Testclass:

import java.awt.Color;

import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.WindowConstants;

public class Ts {

    JFrame frame;
    Comp c1;
    Comp c2;

    public Ts(){
        frame = new JFrame();
        frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        frame.setSize(500,500);
        frame.setBackground(Color.YELLOW);
        //frame.setLayout(null);
        frame.setVisible(true);
        
        
        c1 = new Comp(100,200,"c1");
        c2 = new Comp(200,100,"c2");
        


        frame.add(c1);

        frame.revalidate();
        

        delay();
        frame.add(c2);
        
        frame.revalidate();
        
        
        rmC();
    }

    private void rmC(){
        delay();
        System.out.println("rm c1");
        frame.remove(c1);

        //frame.revalidate();
        frame.repaint();

        delay();

        System.out.println("rm c2");
        frame.remove(c2);

        //frame.revalidate();
        frame.repaint();
    }

    void delay(){
        System.out.println("....dly");
        try{
            Thread.sleep(3000);
        }catch(Exception e){
            e.printStackTrace();
        }
    }
}

that works fine so far (I want to be able to add and remove my test components on command)

Now when I try it with this class:

import java.awt.Color;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.WindowConstants;

public class TsPanel {

    JFrame frame;
    JPanel panel;
    Comp c1;
    Comp c2;

    public TsPanel(){
        frame = new JFrame();
        frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        frame.setSize(500,500);
        frame.setBackground(Color.YELLOW);
        //frame.setLayout(null);
        frame.setVisible(true);

        panel = new JPanel();
        //panel.setLayout(null);
        panel.setBackground(Color.GREEN);

        frame.add(panel);
        
        
        c1 = new Comp(100,200,"c1");
        c2 = new Comp(200,100,"c2");
        


        panel.add(c1);
        //panel.add(new JButton());

        panel.revalidate();
        frame.revalidate();
        frame.repaint();
        

        delay();
        panel.add(c2);
        
        panel.revalidate();
        frame.revalidate();
        frame.repaint();
        
        
        rmC();
    }

    private void rmC(){
        delay();
        System.out.println("rm c1");
        panel.remove(c1);

        //panel.revalidate();
        panel.repaint();
        frame.repaint();

        delay();

        System.out.println("rm c2");
        panel.remove(c2);

        //panel.revalidate();
        panel.repaint();
        frame.repaint();
    }

    void delay(){
        System.out.println("....dly");
        try{
            Thread.sleep(3000);
        }catch(Exception e){
            e.printStackTrace();
        }
    }
}

my components won't display at all (If I null the Layoutmanager the paint methods aren't called at all; if I set the bounds it calls the getPreferredSize() method a few times but also doesn't display anything and if I null the Layoutmanager and set the Bounds the paint methods are called but it still wont show anything). Also if I add buttons or labels to the panel they can be seen, just my Comp class that won't work.

I'd appreciate any help!

I tried to make the View for a Graph displayin program (It's for school; Dijkstra and stuff) that would let me add and remove Vertices and Edges as Components. I thought if I just extended the JComponent class respectively, added the Component, called the panel repaint (appearently not neccesary) and revalidate (if using layoutmanager) methods, the components would show, and if I removed them from the panel (then called the repaint and revalidate methods again) they would go away without me having to redraw everything "manually".

Instead the components just wont show at all if I try to do it on a panel.


Solution

  • In your constructor you specify coordinates your components should draw to. However, the Graphics object handles these coordinates as being relative to the coordinate system of each component. Relevant quote from Graphics API:

    All coordinates that appear as arguments to the methods of this Graphics object are considered relative to the translation origin of this Graphics object prior to the invocation of the method.

    To visualize the boundaries of your components by a rectangle, for instance, you would use (0, 0) and not (getx(), getY()) as the origin: g.drawLine(0, 0, getWidth(), getHeight());. You are drawing to, e.g., (100, 200) while your components have a size of 50 x 50 as specified by getPreferredSize(). So you are effectively drawing outside of your components.

    To use coordinates relative to the parent coordinate system, you could make use of getX() and getY() as these define the origin of your component. There are also various convert methods in the class SwingUtilities allowing to translate coordinates between the coordinate systems of two components or even to translate to absolute screen coordinates. Still you would not see any drawings outside of the boundaries of your component.

    So why does this problem become visible in the JPanel version only, you might ask.

    By default, JPanel (in contrast to JFrame) uses a FlowLayout. This will add your components horizontally in line, one after each other, respecting their preferred size but also centering the group of components. Thus, your components c1 and c2 will roughly end up occupying the space from (200,0) to (300, 50) (not considering margins, padding, and alignment applied by the layout).

    In case of adding the components directly to the JFrame, both your components are put in the BorderLayout.CENTER part of the layouter. If the other parts of the layouter are empty, the center part is assigned more or less the whole space of the parent component and your components are expanded to fill that space (stretching each component beyond its preferred size).

    So either make sure that your components draw relatively to their own coordinate system or try a completely different approach: e.g., draw directly in a custom JPanel and let that panel handle a list of shapes it has to draw.

    Another link covering some basics (in addition to those mentioned by @Gilbert): Painting in AWT and Swing