javaswingdrag-and-dropjpaneljmenubar

Image Icon location resetting on menu bar click


I am trying to drag and drop image icons on the screen but whenever I click the menu bar after moving one it resets to where it was originally. I think that it has something to do with the screen object in the Menu class not updating to reflect the changes from the Window class. Can someone please help?

import java.awt.BasicStroke;
import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.image.BufferedImage;

import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;


public class Tester {
    public Tester()
    {
        SwingUtilities.invokeLater(new Runnable() {

            @Override
            public void run() {
                // TODO Auto-generated method stub
                Window win = new Window();
                win.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            }
        });
    }
    public class Window extends JFrame
    {
        private Menu menu;
        public Window()
        {
            setDefaultLookAndFeelDecorated(true);
            menu = new Menu(this);
            JMenuBar menuBar = menu.getMenuBar();
            setJMenuBar(menuBar);
            add(menu.screen);
            pack();
            setLocationRelativeTo(null);
            setVisible(true);
        }
    }
    public class MouseHandler extends MouseAdapter{

        private Point offset;
        public MouseHandler()
        {
            
        }
        
        @Override
        public void mousePressed(MouseEvent e) {
            offset = e.getPoint();
        }

        @Override
        public void mouseDragged(MouseEvent e) {
            int x = e.getPoint().x - offset.x;
            int y = e.getPoint().y - offset.y;
            JLabel component = (JLabel) e.getComponent();
            Point location = component.getLocation();
            location.x += x;
            location.y += y;
            component.setLocation(location);
        }
        
       
    }
    public class Screen extends JPanel
    {
        private MouseHandler m;
        public Screen()
        {
            setPreferredSize(new Dimension(700, 500));
            m = new MouseHandler();
        }
        public void addObject()
        {
            BufferedImage move = new BufferedImage(20, 20, BufferedImage.TYPE_INT_RGB);
            Graphics2D x = move.createGraphics();
            x.setStroke(new BasicStroke(1));
            x.drawRect(0, 0, 20, 20);
            JFrame f = new JFrame();
            f.pack();
            f.paint(x);
            JLabel lab = new JLabel(new ImageIcon(move));
            lab.addMouseListener(m);
            lab.addMouseMotionListener(m);
            add(lab);
        }
    }
    public class Menu extends JPanel implements ActionListener
    {
        private Window window;
        private JMenuBar menuBar;
        private JMenu menu;
        private JMenuItem menuItem;
        public Screen screen = new Screen();
        public Menu(Window window)
        {
            this.window = window;
            menuBar = new JMenuBar();
            menu = new JMenu("1");
            menuItem = new JMenuItem("2"); 
            menuBar.add(menu);
            menu.add(menuItem);
            menuItem.addActionListener(this);
        }
        @Override
        public void actionPerformed(ActionEvent e) {
            // TODO Auto-generated method stub
            screen.addObject();
            screen.revalidate();
            screen.repaint();
        }
        public JMenuBar getMenuBar()
        {
            return this.menuBar;
        }
        
    }
}

Sorry for the lack of details, I am trying to get this posted before I go to my job. Some of the variables might be redundant because I was trying different things and didn't bother removing them.

I tried changing it so that it would revalidate and redraw but that only got rid of another problem that I was having.

Edit: I have made a simpler version for an MRE. I believe it might have to do with the layout as if I call the revalidate method at all it does the same thing with the object.


Solution

  • The Screen object’s LayoutManager (which happens to be a FlowLayout, since that is the default layout) decides where child components are placed and how big they should be. This process happens when a panel is validated, and can also happen for some other reasons. Each time it occurs, the effects of your component.setLocation(location); call are discarded.

    Applications are not supposed to set components’ locations (or sizes) explicitly. Getting the size of a component, even something as simple as a label, can be a complicated process. Text is rendered based on a font’s point size and the dot-pitch of the monitor. Images might be scaled using the system’s HiDPI settings.

    All of this is handled by Swing. Components are capable of reporting their own preferred sizes, and layout managers will accommodate those sizes and will place things according to a layout manager’s contract. (For example, BorderLayout can place a component in the center, and a component on each side.)

    But you don’t want things placed automatically; you want them to be shown wherever the user has dragged them. Which means, your needs are in direct conflict with all LayoutManagers.

    For this reason, you should not use child components to display your icons. Instead, draw them directly on your JPanel.

    This requires a few things:

    1. A field which holds the data model. This is simply a list of the table data objects, rather than components, that your panel will draw.
    2. A field containing the item currently being dragged, so mouse motion events know which item to move.

    Here is an example:

    import java.io.Serial;
    import java.io.Serializable;
    
    import java.util.Collection;
    import java.util.ArrayList;
    import java.util.Map;
    import java.util.EnumMap;
    import java.util.Optional;
    import java.util.Objects;
    
    import java.awt.EventQueue;
    import java.awt.Dimension;
    import java.awt.Point;
    import java.awt.Color;
    import java.awt.Graphics;
    import java.awt.AWTEvent;
    
    import java.awt.image.BufferedImage;
    
    import java.awt.event.MouseEvent;
    
    import javax.swing.JPanel;
    import javax.swing.JMenu;
    import javax.swing.JMenuBar;
    import javax.swing.JMenuItem;
    import javax.swing.JFrame;
    
    public class TableArranger
    extends JPanel {
        @Serial
        private static final long serialVersionUID = 1;
    
        public enum TableType {
            RED,
            BLUE
        }
    
        public static class Table
        implements Serializable {
            @Serial
            private static final long serialVersionUID = 1;
    
            private final Point location = new Point();
    
            private final TableType type;
    
            public Table(TableType type) {
                Objects.requireNonNull(type, "Type cannot be null.");
                this.type = type;
            }
    
            public TableType getType() {
                return type;
            }
    
            public Point getLocation() {
                return new Point(location);
            }
    
            public void moveTo(Point newLocation) {
                location.setLocation(newLocation);
            }
        }
    
        private final Map<TableType, BufferedImage> icons;
    
        private final Collection<Table> tables = new ArrayList<>();
    
        private Table draggedTable;
    
        private Point relativeDragLocation;
    
        public TableArranger() {
            enableEvents(
                AWTEvent.MOUSE_EVENT_MASK | AWTEvent.MOUSE_MOTION_EVENT_MASK);
    
            BufferedImage redIcon =
                new BufferedImage(20, 20, BufferedImage.TYPE_INT_ARGB);
            Graphics g = redIcon.createGraphics();
            g.setColor(Color.RED);
            g.drawRect(0, 0, 19, 19);
            g.dispose();
    
            BufferedImage blueIcon =
                new BufferedImage(20, 20, BufferedImage.TYPE_INT_ARGB);
            g = blueIcon.createGraphics();
            g.setColor(Color.BLUE);
            g.drawRect(0, 0, 19, 19);
            g.dispose();
    
            this.icons = new EnumMap<>(
                Map.of(TableType.RED, redIcon, TableType.BLUE, blueIcon));
        }
    
        @Override
        public Dimension getPreferredSize() {
            return new Dimension(700, 500);
        }
    
        @Override
        public void paintComponent(Graphics g) {    
            super.paintComponent(g);
    
            for (Table table : tables) {
                Point location = table.getLocation();
                TableType type = table.getType();
    
                BufferedImage icon = icons.get(type);
                g.drawImage(icon, location.x, location.y, this);
            }
        }
    
        private Optional<Table> tableAt(Point point) {
            for (Table table : tables) {
                Point location = table.getLocation();
                TableType type = table.getType();
    
                BufferedImage icon = icons.get(type);
                int width = icon.getWidth();
                int height = icon.getHeight();
                if (point.x >= location.x && point.x < location.x + width &&
                    point.y >= location.y && point.y < location.y + height) {
    
                    return Optional.of(table);
                }
            }
    
            return Optional.empty();
        }
    
        @Override
        protected void processMouseEvent(MouseEvent event) {
            if (event.getButton() == MouseEvent.BUTTON1) {
                int id = event.getID();
                switch (id) {
                    case MouseEvent.MOUSE_PRESSED:
                        Point clickLocation = event.getPoint();
                        Optional<Table> clickedTable = tableAt(clickLocation);
                        if (clickedTable.isPresent()) {
                            draggedTable = clickedTable.get();
    
                            Point tableLocation = draggedTable.getLocation();
                            relativeDragLocation = new Point(
                                clickLocation.x - tableLocation.x,
                                clickLocation.y - tableLocation.y);
    
                            event.consume();
                        }
                        break;
                    case MouseEvent.MOUSE_RELEASED:
                        draggedTable = null;
                        relativeDragLocation = null;
                        break;
                    default:
                        break;
                }
            }
    
            super.processMouseEvent(event);
        }
    
        @Override
        protected void processMouseMotionEvent(MouseEvent event) {
            int modifiers = event.getModifiersEx();
            if (event.getID() == MouseEvent.MOUSE_DRAGGED &&
                (modifiers & MouseEvent.BUTTON1_DOWN_MASK) != 0 &&
                draggedTable != null) {
    
                Point oldLocation = draggedTable.getLocation();
                TableType type = draggedTable.getType();
    
                BufferedImage icon = icons.get(type);
                int width = icon.getWidth();
                int height = icon.getHeight();
    
                Point newLocation = event.getPoint();
                newLocation.x -= relativeDragLocation.x;
                newLocation.y -= relativeDragLocation.y;
                draggedTable.moveTo(newLocation);
    
                repaint(oldLocation.x, oldLocation.y, width, height);
                repaint(newLocation.x, newLocation.y, width, height);
    
                event.consume();
            }
    
            super.processMouseMotionEvent(event);
        }
    
        public void addTable(TableType type) {
            Table table = new Table(type);
    
            // Place in center of panel, initially.
            table.moveTo(new Point(getWidth() / 2, getHeight() / 2));
    
            tables.add(table);
            repaint();
        }
    
        static void showInWindow() {
            TableArranger arranger = new TableArranger();
    
            JMenuItem addRed = new JMenuItem("Add Red");
            addRed.addActionListener(e -> arranger.addTable(TableType.RED));
    
            JMenuItem addBlue = new JMenuItem("Add Blue");
            addBlue.addActionListener(e -> arranger.addTable(TableType.BLUE));
    
            JMenu menu = new JMenu("Table");
            menu.add(addRed);
            menu.add(addBlue);
    
            JMenuBar menuBar = new JMenuBar();
            menuBar.add(menu);
    
            JFrame frame = new JFrame("Table Arranger");
            frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    
            frame.setJMenuBar(menuBar);
            frame.getContentPane().add(arranger);
    
            frame.pack();
            frame.setLocationByPlatform(true);
            frame.setVisible(true);
        }
    
        public static void main(String[] args) {
            EventQueue.invokeLater(() -> showInWindow());
        }
    }