Search code examples
javaswingjframejlabel

Can't get absolute JLabel location


My JFrame uses a BorderLayout and it has a JLabel nested in several panels with different layout managers. I've tried several methods, however, cannot get the true position of where it sits in the frame.

I made a test UI and it seems like when other components are added the getX and getY parameters do not update. Other methods like getLocation do not provide a correct result either. Is there any way to obtain the exact location without manually calculating every possible offset from each component.

I am tracking the stated positions of the label (content) using a similar sized panel called content2 in the glass pane which I want to sit underneath content perfectly.

public class test {
    private Dimension pSize = new Dimension(100,100);
    private JFrame frame = new JFrame();
    
    public static void main(String[] args) {
        new test();

    }
    
    public test() {
        
        
        //setup frame basics
        frame.setPreferredSize(new Dimension(500,500));
        frame.setLayout(new BorderLayout());
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        
        // setup GUI
        JMenuBar j = new JMenuBar();
        JMenuItem a = new JMenuItem("lol");
        j.add(a);
        
        JPanel j2 = new JPanel();
        
        
        
        //setup main panel
        JPanel main = new JPanel();
        main.setBackground(Color.DARK_GRAY);
        //setup side panel
        FlowLayout f1 = new FlowLayout(FlowLayout.LEADING);
        f1.setHgap(10);
        f1.setVgap(0);
        JPanel side = new JPanel();
        side.setLayout(new BorderLayout());
        side.setBackground(Color.gray);
        side.setPreferredSize(new Dimension(150,100));
    
        //setup JLabel (the main focus)
        JLabel content = new JLabel("a");
        content.setOpaque(true);
        content.setBackground(Color.blue);
        content.setPreferredSize(pSize);
        
        // Setup the internal panels of side
        JPanel top = new JPanel();//The panel where CONTENT is, the main focus 
        JPanel bot = new JPanel();
        top.setBackground(Color.WHITE); 
        bot.setBackground(Color.orange);
        top.setLayout(f1);
        top.add(content); 
        
        side.add(top, BorderLayout.NORTH); 
        side.add(bot, BorderLayout.CENTER);
        
        frame.add(main, BorderLayout.CENTER);
        frame.add(side, BorderLayout.WEST);
        frame.add(j2, BorderLayout.NORTH);
        frame.setJMenuBar(j);
        
        frame.pack();
        frame.setVisible(true);
        
        //Setting up the glass panel
        JPanel pane = new JPanel();
        pane.setLayout(null);
        pane.setOpaque(false);
        JPanel content2 = new JPanel();
        content2.setBackground(Color.red);
        
        content.revalidate();
        int x = content.getX();
        int y = content.getY();
        
        
        
        // y = (int) content.getLocation().getY(); //returns a completely wrong location
        //y = (int) content.getLocationOnScreen(); //returns a completely wrong location
        
        /*
        Point p = new Point();
        p.setLocation(x, y);
        
        p = SwingUtilities.convertPoint(content2, x, y, frame);
        
        //SwingUtilities.convertPoint(content, p, frame);
        
        y = (int) p.getY();
        
        
         * 
         * Tried multiple SwingUtility converions to no avail
         * 
         */
        
        // y = y +j.getHeight() + j2.getHeight(); // Manually calculating the Y off set works successfully but is too tedious for large project
        y = y + content.getHeight();
        content2.setBounds(x,y,100,100);
        
        pane.add(content2);
        
        frame.setGlassPane(pane);
        frame.getGlassPane().setVisible(true);
        frame.pack();
        
    }

}
    //frame.getContentPane().add(content);
        //frame.add(content);
        
        frame.setPreferredSize(new Dimension(500,500));
        
        content.setBorder(BorderFactory.createEmptyBorder());
        side.setLayout(new BorderLayout());
        
        JPanel top = new JPanel();
        JPanel bot = new JPanel();
        top.setBackground(Color.WHITE);
        bot.setBackground(Color.orange);
        
        side.add(top, BorderLayout.NORTH);
        
        top.setLayout(f1);
        top.add(content);
        
        side.add(bot, BorderLayout.CENTER);

        frame.add(main, BorderLayout.CENTER);
        frame.add(j2, BorderLayout.NORTH);
        frame.add(side, BorderLayout.WEST);
        
        frame.pack();
        frame.setVisible(true);
        JPanel pane = new JPanel();
        pane.setLayout(null);
        pane.setOpaque(false);
        JPanel content2 = new JPanel();
        content2.setBackground(Color.red);
        
        content.revalidate();
        int x = content.getX();
        int y = content.getY();
        // y = y +j.getHeight() + j2.getHeight();
         
         
        
        
         
        y = y + content.getHeight();
        content2.setBounds(x,y,100,100);
        
        pane.add(content2);
        
        frame.setGlassPane(pane);
        frame.getGlassPane().setVisible(true);
        frame.pack();
        
    }

}

Solution

  • Conceptually you could make use of SwingUtilities.convertPoint or SwingUtilities.convertRectangle to convert between container contexts, for example...

    enter image description here

    import java.awt.Color;
    import java.awt.EventQueue;
    import java.awt.Graphics;
    import java.awt.Graphics2D;
    import java.awt.GridBagLayout;
    import java.awt.Rectangle;
    import java.util.ArrayList;
    import java.util.List;
    import javax.swing.JComponent;
    import javax.swing.JFrame;
    import javax.swing.JLabel;
    import javax.swing.JPanel;
    import javax.swing.SwingUtilities;
    import javax.swing.border.EmptyBorder;
    
    public class Main {
        public static void main(String[] args) {
            new Main();
        }
    
        public Main() {
            EventQueue.invokeLater(new Runnable() {
                @Override
                public void run() {
                    GlassPane glassPane = new GlassPane();
                    JFrame frame = new JFrame();
                    frame.setGlassPane(glassPane);
                    frame.add(new MainPane(glassPane));
                    glassPane.setVisible(true);
                    frame.pack();
                    frame.setLocationRelativeTo(null);
                    frame.setVisible(true);
                }
            });
        }
    
        public interface Tracker {
            public void addTrackable(Trackable trackable);
            public void removeTrackable(Trackable trackable);
        }
    
        public interface Trackable {
            public JComponent[] getTrackedComponents();
        }
    
        public class MainPane extends JPanel {
    
            private JLabel label = new JLabel("Catch me if you can");
    
            public MainPane(Tracker tracker) {
                setLayout(new GridBagLayout());
                setBorder(new EmptyBorder(32, 32, 32, 32));
                add(label);
    
                tracker.addTrackable(new Trackable() {
                    @Override
                    public JComponent[] getTrackedComponents() {
                        return new JComponent[] { label };
                    }
                });
            }
    
        }
    
        public class GlassPane extends JPanel implements Tracker {
    
            private List<Trackable> trackables = new ArrayList<>(8);
    
            public GlassPane() {
                setOpaque(false);
            }
    
            @Override
            public void addTrackable(Trackable trackable) {
                trackables.add(trackable);
                revalidate();
                repaint();
            }
    
            @Override
            public void removeTrackable(Trackable trackable) {
                trackables.remove(trackable);
                revalidate();
                repaint();
            }
    
            @Override
            protected void paintComponent(Graphics g) {
                super.paintComponent(g);
                Graphics2D g2d = (Graphics2D) g.create();
                g2d.setColor(Color.RED);
                for (Trackable trackable : trackables) {
                    for (JComponent component : trackable.getTrackedComponents()) {
                        Rectangle relativeBounds = SwingUtilities.convertRectangle(component.getParent(), component.getBounds(), this);
                        g2d.draw(relativeBounds);
                    }
                }
                g2d.dispose();
            }
    
        }
    }
    

    Well, that's pretty boring, it's one component inside one container, let's trying something a little more complicated...

    enter image description here

    import java.awt.Color;
    import java.awt.EventQueue;
    import java.awt.Graphics;
    import java.awt.Graphics2D;
    import java.awt.GridBagLayout;
    import java.awt.GridLayout;
    import java.awt.Rectangle;
    import java.util.ArrayList;
    import java.util.Arrays;
    import java.util.List;
    import javax.swing.JComponent;
    import javax.swing.JFrame;
    import javax.swing.JLabel;
    import javax.swing.JPanel;
    import javax.swing.SwingUtilities;
    import javax.swing.border.CompoundBorder;
    import javax.swing.border.EmptyBorder;
    import javax.swing.border.LineBorder;
    
    public class Main {
        public static void main(String[] args) {
            new Main();
        }
    
        public Main() {
            EventQueue.invokeLater(new Runnable() {
                @Override
                public void run() {
                    GlassPane glassPane = new GlassPane();
                    JFrame frame = new JFrame();
                    frame.setLayout(new GridLayout(2, 2, 8, 8));
    
                    frame.add(new MainPane(glassPane));
                    frame.add(new MainPane(glassPane));
                    frame.add(new MainPane(glassPane));
                    frame.add(new MainPane(glassPane));
    
                    frame.setGlassPane(glassPane);
                    glassPane.setVisible(true);
                    frame.pack();
                    frame.setLocationRelativeTo(null);
                    frame.setVisible(true);
                }
            });
        }
    
        public interface Tracker {
            public void addTrackable(Trackable trackable);
    
            public void removeTrackable(Trackable trackable);
        }
    
        public interface Trackable {
            public JComponent[] getTrackedComponents();
        }
    
        public class MainPane extends JPanel {
    
            private JLabel label = new JLabel("Catch me if you can");
    
            public MainPane(Tracker tracker) {
                setLayout(new GridBagLayout());
                setBorder(new CompoundBorder(new LineBorder(Color.DARK_GRAY, 1, true), new EmptyBorder(32, 32, 32, 32)));
                add(label);
    
                tracker.addTrackable(new Trackable() {
                    @Override
                    public JComponent[] getTrackedComponents() {
                        return new JComponent[]{label};
                    }
                });
            }
    
        }
    
        public class GlassPane extends JPanel implements Tracker {
    
            private List<Trackable> trackables = new ArrayList<>(8);
            private List<Color> masterColors = new ArrayList<>(Arrays.asList(new Color[]{
                Color.RED,
                Color.GREEN,
                Color.BLUE,
                Color.CYAN,
                Color.DARK_GRAY,
                Color.GRAY,
                Color.MAGENTA,
                Color.ORANGE,
                Color.PINK,
                Color.YELLOW,}));
    
            public GlassPane() {
                setOpaque(false);
            }
    
            @Override
            public void addTrackable(Trackable trackable) {
                trackables.add(trackable);
                revalidate();
                repaint();
            }
    
            @Override
            public void removeTrackable(Trackable trackable) {
                trackables.remove(trackable);
                revalidate();
                repaint();
            }
    
            @Override
            protected void paintComponent(Graphics g) {
                super.paintComponent(g);
                Graphics2D g2d = (Graphics2D) g.create();
                g2d.setColor(Color.RED);
                List<Color> colors = new ArrayList<>(masterColors);
                for (Trackable trackable : trackables) {
                    for (JComponent component : trackable.getTrackedComponents()) {
                        if (colors.isEmpty()) {
                            colors = new ArrayList<>(masterColors);
                        }
                        g2d.setColor(colors.remove(0));
                        Rectangle relativeBounds = SwingUtilities.convertRectangle(component.getParent(), component.getBounds(), this);
                        g2d.draw(relativeBounds);
                    }
                }
                g2d.dispose();
            }
    
        }
    }
    

    Here is a new smipler example program, trying to keep as close to your code as possible, that uses the convertRectangle but I can't manage to run it correctly

    int y = (int) (r.getY() + r.getHeight()); ... are you deliberately trying to offset the "overlay"? This seems weird to me.

    Another issue is, how does the GlassPane know when the child has changed position/size

    So, I modified your code, getting rid of the "modification" to the x/y position (so I'm 100% sure that the conversion between context spaces is correct) and added a ComponentListener to monitor changes to the "target" component

    enter image description here

    import java.awt.Color;
    import java.awt.EventQueue;
    import java.awt.Rectangle;
    import javax.swing.JFrame;
    import javax.swing.JLabel;
    import javax.swing.JPanel;
    import javax.swing.SwingUtilities;
    import java.awt.*;
    import java.awt.event.ComponentAdapter;
    import java.awt.event.ComponentEvent;
    
    public class Main {
        private Dimension pSize = new Dimension(100, 100);
        private JFrame frame = new JFrame();
        private JLabel content = new JLabel("Grief");
        private JPanel content2 = new JPanel();
        private SidePane sidePane = new SidePane();
        private GlassPane glass = new GlassPane();
    
        private Menu menu = new Menu();
    
        public static void main(String[] args) {
            new Main();
    
        }
    
        public Main() {
    
            EventQueue.invokeLater(new Runnable() {
                @Override
                public void run() {
                    content.setBackground(Color.green);
                    content.setPreferredSize(pSize);
                    content.setOpaque(true);
                    //setup frame basics
                    frame.setPreferredSize(new Dimension(500, 500));
                    frame.setLayout(new BorderLayout());
                    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                    frame.setLocationRelativeTo(null);
    
                    frame.setGlassPane(glass);
    
                    frame.add(new MainPane());
    //                glass.setNewLocation();
    //                glass.revalidate();
                    frame.getGlassPane().setVisible(true);
    //                glass.setNewLocation();
    
                    frame.pack();
                    frame.setVisible(true);
                }
            });
    
        }
    
        public class MainPane extends JPanel {
            public MainPane() {
                //this.setBackground(Color.orange);
    
                this.setLayout(new BorderLayout());
                this.add(sidePane, BorderLayout.WEST);
                this.add(menu, BorderLayout.NORTH);
            }
    
        }
    
        public class SidePane extends JPanel {
            public SidePane() {
    
                FlowLayout f1 = new FlowLayout(FlowLayout.LEADING);
                this.setLayout(f1);
                this.setBackground(Color.blue);
    
                this.add(content);
    
            }
        }
    
        public class Menu extends JPanel {
            public Menu() {
                this.setBackground(Color.orange);
            }
    
        }
    
        public class GlassPane extends JPanel {
    
            private Rectangle target;
    
            public GlassPane() {
                this.setOpaque(false);
                setLayout(null);
                content2.setBackground(Color.BLACK);
                content2.setPreferredSize(pSize);
                content2.setOpaque(true);
                add(content2);
    
                content.addComponentListener(new ComponentAdapter() {
                    @Override
                    public void componentResized(ComponentEvent e) {
                        updateOverlay();
                    }
    
                    @Override
                    public void componentMoved(ComponentEvent e) {
                        updateOverlay();
                    }
                });
            }
    
            protected void updateOverlay() {
    //            Rectangle t = new Rectangle();
    //            t.setBounds((int) content.getLocation().getX(), (int) content.getLocation().getY(), content.getWidth(), content.getHeight());
    //            Rectangle r = SwingUtilities.convertRectangle(content.getParent(), content.getBounds(), this);
    //            Rectangle r = SwingUtilities.convertRectangle(content.getParent(), content.getBounds(), this);
                target = SwingUtilities.convertRectangle(content.getParent(), content.getBounds(), this);
    
                content2.setBounds(target);
    
                //   r = SwingUtilities.convertRectangle(content.getParent(), t, this);
    //            int x = (int) r.getBounds().getX();
    //            x = (int) r.getX();
    //            int y = (int) (r.getY() + r.getHeight());
    //
    //            content2.setBounds(x, y, 100, 100);
    //            this.add(content2);
            }
    
            @Override
            public void paint(Graphics g) {
                super.paint(g);
                Graphics2D g2d = (Graphics2D) g.create();
                if (target != null) {
                    g2d.setColor(Color.RED);
                    g2d.draw(target);
                }
                g2d.dispose();
            }
    
        }
    
    }