How to create moveable JPanel using BorderLayout?

Tags: , ,



I want to create a GUI with a sliding Menu. The menu is one of three JPanels organized onto a JFrame with BorderLayout. The Menu in question is on the west side. How can I make it so that the menu can hold Textfields, JLabels, images etc. while having the ability to slide in and out?

Answer

This can be done, but it requires either a custom layout or a null layout. In the latter case, you’ll need to manage the child component’s sizes and placement yourself.

Since a Component “traps” the mouse once a button is pressed, and all mouse events get sent to that same Component until the mouse is released, you only need to listen for mouse drag events on the one Component that serves as the drag handle.

The slidable area, which I’ll call a drawer, can be in the center of a BorderLayout, with the drag handle in the east (or LINE_END) position.

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Point;

import java.awt.event.ComponentEvent;
import java.awt.event.ComponentAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseMotionAdapter;

import javax.swing.JComponent;
import javax.swing.JLayeredPane;
import javax.swing.JPanel;

public class Drawer {
    private final JLayeredPane container;

    private final JComponent main;

    private final JComponent contentAndHandle;

    private int dragStartX;

    private int drawerPositionOnPress;

    public Drawer(JComponent drawerContent,
                  JComponent main) {

        this.main = main;

        container = new JLayeredPane();
        container.add(main, JLayeredPane.DEFAULT_LAYER);

        // Do the work normally done by a LayoutManager.
        main.addHierarchyListener(e -> {
            EventQueue.invokeLater(
                () -> container.setPreferredSize(main.getPreferredSize()));
        });
        container.addComponentListener(new ComponentAdapter() {
            @Override
            public void componentResized(ComponentEvent event) {
                EventQueue.invokeLater(() -> updateChildrenSizes());
            }
        });

        JComponent handle = new JPanel() {
            @Override
            public void paintComponent(Graphics g) {
                super.paintComponent(g);

                int width = getWidth();
                int height = getHeight();
                for (int x = 0; x < width; x += 4) {
                    for (int y = 0; y < height; y += 4) {
                        g.setColor(Color.WHITE);
                        g.fillRect(x, y, 1, 1);
                        g.fillRect(x + 2, y + 2, 1, 1);
                        g.setColor(Color.BLACK);
                        g.fillRect(x + 1, y + 1, 1, 1);
                        g.fillRect(x + 3, y + 3, 1, 1);
                    }
                }
            }
        };
        handle.setPreferredSize(new Dimension(12, 1));
        handle.setOpaque(true);
        handle.addMouseListener(new MouseAdapter() {
            @Override
            public void mousePressed(MouseEvent event) {
                if (event.getButton() == MouseEvent.BUTTON1) {
                    dragStartX = event.getXOnScreen();
                    drawerPositionOnPress =
                        contentAndHandle.getLocationOnScreen().x;
                }
            }
        });
        handle.addMouseMotionListener(new MouseMotionAdapter() {
            @Override
            public void mouseDragged(MouseEvent event) {
                int modifiers = event.getModifiersEx();
                if ((modifiers & MouseEvent.BUTTON1_DOWN_MASK) != 0) {
                    int newX = drawerPositionOnPress + 
                        event.getXOnScreen() - dragStartX;

                    newX = Math.min(0, newX);
                    newX = Math.max(-drawerContent.getWidth(), newX);

                    Point newPosition = new Point(newX, 0);
                    EventQueue.invokeLater(
                        () -> contentAndHandle.setLocation(newPosition));
                }
            }
        });

        contentAndHandle = new JPanel(new BorderLayout());
        contentAndHandle.add(drawerContent, BorderLayout.CENTER);
        contentAndHandle.add(handle, BorderLayout.LINE_END);

        container.add(contentAndHandle, JLayeredPane.DRAG_LAYER);

        // Do the work normally done by a LayoutManager.
        container.setPreferredSize(main.getPreferredSize());
        updateChildrenSizes();
    }

    public JComponent getComponent() {
        return container;
    }

    private void updateChildrenSizes() {
        Dimension size = container.getSize();
        main.setSize(size);
        size.width = contentAndHandle.getPreferredSize().width;
        contentAndHandle.setSize(size);
        container.validate();
    }
}

I chose to draw a pattern in the drag handle based on the “drag texture” described in the old Java Look and Feel Design Guidelines. You can of course use whatever appearance you want, including images.

Here is an example use of the class:

import java.awt.EventQueue;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;

import javax.swing.BorderFactory;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextField;
import javax.swing.JTable;

public class DrawerTest {
    public static void main(String[] args) {
        EventQueue.invokeLater(() -> {
            JPanel exampleMenu = new JPanel(new GridBagLayout());
            exampleMenu.setBorder(
                BorderFactory.createEmptyBorder(12, 12, 12, 12));

            GridBagConstraints gbc = new GridBagConstraints();
            gbc.anchor = GridBagConstraints.BASELINE_LEADING;
            exampleMenu.add(new JLabel("First name: "), gbc);
            gbc.gridwidth = GridBagConstraints.REMAINDER;
            exampleMenu.add(new JTextField(10), gbc);

            gbc.insets.top = 3;
            gbc.gridwidth = 1;
            exampleMenu.add(new JLabel("Last name: "), gbc);
            gbc.gridwidth = GridBagConstraints.REMAINDER;
            exampleMenu.add(new JTextField(10), gbc);

            gbc.gridwidth = 1;
            exampleMenu.add(new JLabel("Credit card: "), gbc);
            gbc.gridwidth = GridBagConstraints.REMAINDER;
            exampleMenu.add(new JTextField(19), gbc);

            gbc.gridwidth = 1;
            exampleMenu.add(new JLabel("Address: "), gbc);
            gbc.gridwidth = GridBagConstraints.REMAINDER;
            exampleMenu.add(new JTextField(19), gbc);

            JComponent mainContent;
            mainContent = new JScrollPane(new JTable(
                new Object[][] {
                    { "Stack Overflow", "https://stackoverflow.com" },
                    { "Software Engineering",
                            "https://softwareengineering.stackexchange.com" },
                    { "Code Review", "https://codereview.stackexchange.com" },
                    { "Super User", "https://superuser.com" },
                    { "Server Fault", "https://serverfault.com" },
                },
                new Object[] { "Name", "URL" }));

            Drawer drawer = new Drawer(exampleMenu, mainContent);

            JFrame frame = new JFrame("Drawer Example");
            frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            frame.getContentPane().add(drawer.getComponent());
            frame.pack();
            frame.setLocationByPlatform(true);
            frame.setVisible(true);
        });
    }
}


Source: stackoverflow