Search code examples
javaswingjpaneljscrollpanejcomponent

JPanel with JComponents inside a JScrollPane and custom LayoutManager


I have written a subclass of JComponent which draws itself in the paintComponent method. In the constructor I set the bounds with setBounds. This works fine. I added this subclass to a JPanel. This JPanel is inside a JScrollPane. This JPanel is an class subclassing JPanel and implementing MouseInputListener and KeyListener. So whenever a user clicks in the JPanel the bounds of each JComponent is check against mouse point. If matched the focus is set to that JComponent. If the focus is at the JComponent and the user drags the mouse the JComponent is moved inside the JPanel. What I want to achive is that whenever the position of a JComponent inside the JPanel changes and gets out of viewport of JScrollPane that the scrollbars gets visible and the JPanel gets resized so the user can scroll outside of the viewable area. To achive this I have written a custom LayoutManager which does not layout the JComponents in any way but the absolute position. But whenever I move a JComponent inside the JPanel and outside of the viewable area the scrollbars do not get updated. How can I achive this?

Here is the class subclassig JComponent:

public abstract class Gate extends JComponent {

/**
 * 
 */
private static final long serialVersionUID = 1L;

public Gate() {
    super();
    setBounds(new Rectangle(0, 0, 100, 100));
    setBorder(BorderFactory.createLineBorder(Color.black));
    setFocusable(true);
    setOpaque(false);
    setBackground(Color.white);
}

@Override
protected void paintComponent(Graphics g) {
    super.paintComponent(g);
    if(hasFocus()) {
        setBorder(BorderFactory.createLineBorder(Color.green));
    } else {
        setBorder(BorderFactory.createLineBorder(Color.black));
    }
    Graphics g2 = g.create();
    g2.setColor(getBackground());
    g2.fillRect(0, 0, getWidth(), getHeight());
    g2.dispose();
}
}

Here is the class subclassing JPanel:

public class GatesPanel extends JPanel implements MouseInputListener, KeyListener {

/**
 * 
 */
private static final long serialVersionUID = 1L;

public GatesPanel() {
    super();
    setLayout(new GateLayout());
    addMouseListener(this);
    addMouseMotionListener(this);
    addKeyListener(this);
    setFocusable(true);
}

@Override
public void mouseClicked(MouseEvent e) {
    if(e.getButton() == MouseEvent.BUTTON1) {
        for(int i = 0; i < getComponentCount(); i++) {
            Component c = getComponent(i);
            if(c instanceof JComponent) {
                JComponent j = (JComponent)c;
                if(j.getBounds().contains(e.getPoint())) {
                    if(j.hasFocus()) {
                        grabFocus();
                    } else {
                        j.grabFocus();
                    }
                }
            }
        }
    }
}

@Override
public void mouseEntered(MouseEvent e) {

}

@Override
public void mouseExited(MouseEvent e) {

}

@Override
public void mousePressed(MouseEvent e) {

}

@Override
public void mouseReleased(MouseEvent e) {

}

@Override
public void mouseDragged(MouseEvent e) {

}

@Override
public void mouseMoved(MouseEvent e) {
    for(int i = 0; i < getComponentCount(); i++) {
        Component c = getComponent(i);
        if(c.hasFocus()) {
            c.setLocation(e.getPoint());
        }
    }
}

@Override
public void keyPressed(KeyEvent e) {

}

@Override
public void keyReleased(KeyEvent e) {

}

@Override
public void keyTyped(KeyEvent e) {

}
}

And here is the class implementing LayoutManager:

public class GateLayout implements LayoutManager {

private int minWidth;
private int minHeight;
private int preferedWidth;
private int preferedHeight;

public GateLayout() {
    minWidth = 0;
    minHeight = 0;
    preferedWidth = 0;
    preferedHeight = 0;
}

@Override
public void addLayoutComponent(String name, Component component) {

}

@Override
public void layoutContainer(Container parent) { 
    setSize(parent);
}

private void setSize(Container parent) {
    minWidth = 0;
    minHeight = 0;
    preferedWidth = 0;
    preferedHeight = 0;
    for(int i = 0; i < parent.getComponentCount(); i++) {
        Component c = parent.getComponent(i);
        Dimension d = c.getPreferredSize();
        minWidth = Math.max(d.width, minWidth);
        minHeight = Math.max(d.height, minHeight);
        preferedWidth += d.width;
        preferedHeight += d.height;
    }
}

@Override
public Dimension minimumLayoutSize(Container parent) {
    setSize(parent);
    Insets insets = parent.getInsets();
    return new Dimension(minWidth + insets.left + insets.right,
                        minHeight + insets.top + insets.bottom);
}

@Override
public Dimension preferredLayoutSize(Container parent) {
    setSize(parent);
    Insets insets = parent.getInsets();
    return new Dimension(preferedWidth + insets.left + insets.right,
                         preferedHeight + insets.top + insets.bottom);
}

@Override
public void removeLayoutComponent(Component component) {

}

}

In the JFrame subclass called GatesFrame I call the following code:

gatesPanel = new GatesPanel();
JScrollPane scrollPane = new JScrollPane(gatesPanel);
scrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
scrollPane.setAutoscrolls(true);
setContentPane(scrollPane);

After this I add a Gate subclass called GateAND to the GatesPanel:

gatesPanel.add(new GateAND());

So what am I doing wrong that the scrollbars gets not updated when user moves the component inside the panel surrounded by a scroll pane? Any help is welcome.

Greez,

maxpa1n87


Solution

  • To achive this I have written a custom LayoutManager which does not layout the JComponents in any way but the absolute position. But whenever I move a JComponent inside the JPanel and outside of the viewable area the scrollbars do not get updated.

    Maybe your layout manager is not setting the preferred size correctly. The scrollbars will appear automatically when the preferred size is greater than the size of the scrollpane.

    You also need to make sure that when you are finished dragging the component that you invoke revalidate() on the panel so that the layout manager is invoked.

    Check out the Drag Layout which was designed for this purpose.

    Here is a simple example:

    import java.awt.*;
    import java.awt.event.*;
    import javax.swing.*;
    import javax.swing.event.*;
    import javax.swing.border.*;
    
    public class DragLayoutTest
    {
        public static void main( String[] args )
        {
            DragListener drag = new DragListener();
    
            DragLayout dl = new DragLayout();
            dl.setUsePreferredSize(false);
    
            JPanel panel = new JPanel( dl );
            panel.setBorder( new MatteBorder(10, 10, 10, 10, Color.YELLOW) );
    
            createLabel(drag, panel, "North", 150, 0);
            createLabel(drag, panel, "West", 0, 100);
            createLabel(drag, panel, "East", 300, 100);
            createLabel(drag, panel, "South", 150, 200);
            createLabel(drag, panel, "Center", 150, 100);
    
            JFrame.setDefaultLookAndFeelDecorated(true);
            JFrame frame = new JFrame( "Drag Layout" );
            frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            frame.add( new JScrollPane(panel) );
            frame.pack();
            frame.setLocationRelativeTo( null );
            frame.setVisible( true );
        }
    
        public static void createLabel(MouseInputAdapter drag, JPanel panel, String text, int x, int y)
        {
            JLabel label = new JLabel( text );
            label.setOpaque(true);
            label.setBackground( Color.ORANGE );
            label.setLocation(x, y);
            panel.add( label );
            label.addMouseListener( drag );
            label.addMouseMotionListener( drag );
        }
    
        static class DragListener extends MouseInputAdapter
        {
            Point location;
            MouseEvent pressed;
    
            public void mousePressed(MouseEvent me)
            {
                pressed = me;
            }
    
            public void mouseDragged(MouseEvent me)
            {
                Component component = me.getComponent();
                location = component.getLocation(location);
                int x = location.x - pressed.getX() + me.getX();
                int y = location.y - pressed.getY() + me.getY();
                component.setLocation(x, y);
                ((JComonent)component.getParent()).revalidate();
             }
        }
    }