Search code examples
javaeclipseswingjcheckboxwindowbuilder

Draw a line between 2 JCheckBox objects in Java Eclipse WindowBuilder


I have some checkboxes on a form and I want them to be connected by single lines. Is there some easy way to do this? Since I'm using WindowBuilder and the objects could move around as the form size changes, I can't just put a graphic on the screen.

Specifically, I have 4 checkboxes representing bases on a baseball diamond, in a diamond pattern. I want the base paths connecting them to be straight lines.


Solution

  • So, the basic concept is:

    • You need to be able to calculate the centre position of a component, based on it's location within the parent container
    • You need someway to be able to paint beneath the components, so the line isn't rendered over the top of them.

    This would suggest that the JCheckBoxs need to be contained within the same component/container, which would just make it simpler to work with.

    From there, you'd override the component's paintComponent method, calculate the centre positions of the source and target components/check boxes and draw a line between

    For example...

    Connected points

    import java.awt.Dimension;
    import java.awt.EventQueue;
    import java.awt.Graphics;
    import java.awt.Graphics2D;
    import java.awt.GridBagConstraints;
    import java.awt.GridBagLayout;
    import java.awt.Point;
    import java.awt.Rectangle;
    import javax.swing.JCheckBox;
    import javax.swing.JFrame;
    import javax.swing.JPanel;
    import javax.swing.UIManager;
    import javax.swing.UnsupportedLookAndFeelException;
    
    public class Test {
    
        public static void main(String[] args) {
            new Test();
        }
    
        public Test() {
            EventQueue.invokeLater(new Runnable() {
                @Override
                public void run() {
                    try {
                        UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                    } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                        ex.printStackTrace();
                    }
    
                    JFrame frame = new JFrame("Testing");
                    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                    frame.add(new TestPane());
                    frame.pack();
                    frame.setLocationRelativeTo(null);
                    frame.setVisible(true);
                }
            });
        }
    
        public class TestPane extends JPanel {
    
            private JCheckBox[] checkBoxes;
    
            public TestPane() {
                setLayout(new GridBagLayout());
                GridBagConstraints gbc = new GridBagConstraints();
                gbc.fill = gbc.BOTH;
                gbc.weightx = 1;
                gbc.weighty = 1;
    
                checkBoxes = new JCheckBox[4];
                for (int index = 0; index < 4; index++) {
                    checkBoxes[index] = new JCheckBox();
                    checkBoxes[index].setOpaque(false);
                    checkBoxes[index].setHorizontalAlignment(JCheckBox.CENTER);
                }
    
                // Base
                gbc.gridx = 1;
                gbc.gridy = 2;
                add(checkBoxes[0], gbc);
    
                // First
                gbc.gridx = 2;
                gbc.gridy = 1;
                add(checkBoxes[1], gbc);
    
                // Second
                gbc.gridx = 1;
                gbc.gridy = 0;
                add(checkBoxes[2], gbc);
    
                // Third
                gbc.gridx = 0;
                gbc.gridy = 1;
                add(checkBoxes[3], gbc);
            }
    
            @Override
            public Dimension getPreferredSize() {
                return new Dimension(200, 200);
            }
    
            protected Point center(Rectangle bounds) {
                Point point = new Point();
                point.x = bounds.x + (bounds.width / 2);
                point.y = bounds.y + (bounds.height / 2);
                return point;
            }
    
            protected void paintComponent(Graphics g) {
                super.paintComponent(g);
                Graphics2D g2d = (Graphics2D) g.create();
                Point from = center(checkBoxes[0].getBounds());
                for (int index = 1; index < checkBoxes.length; index++) {
                    Rectangle bounds = checkBoxes[index].getBounds();
                    Point to = center(bounds);
                    g2d.drawLine(from.x, from.y, to.x, to.y);
                    from = to;
                }
                Point to = center(checkBoxes[0].getBounds());
                g2d.drawLine(from.x, from.y, to.x, to.y);
                g2d.dispose();
            }
    
        }
    
    }
    

    Caveat: Because of the way painting works in Swing, when a child component is updated/repainted, it's possible that the parent component won't be notified/painted. So, any changes you make to any of the child components, which might affect the position of the lines, needs to also force a repaint of the parent component as well

    And no, you can't do this through Window Builder, you're going to have to get your hands dirty to make this work properly