javaswingmouselistenerjava-5jviewport

Temporarily disable or prevent repainting JViewPort on scrolling with a mouseDrag


I have written a MouseListener as defined below so that I can move a JButton around to reorder the components that are within the JPanel. The JPanel is within a JScrollPane so that when multiple components are added they can be scrolled.

The problem I have is that when dragging the component and the mouse goes out of the scrollpane/viewport then the component will snap back to its position within the JPanel then will be drawn in the correct location. I assume that this behavior is due to the Viewport calling a repaint of its children when I call scrollRectToVisible()

Is there a way that I can prevent this from happening?

Please note that I am limited to Java 5

Listener

import java.awt.Component;
import java.awt.Container;
import java.awt.MouseInfo;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.MouseEvent;
import javax.swing.JViewport;
import javax.swing.SwingUtilities;
import javax.swing.event.MouseInputAdapter;

public class DragListener extends MouseInputAdapter
{
    private Point location;
    private MouseEvent pressed;
    private MouseEvent dragged;
    private MouseEvent dropped;

    @Override
    public void mousePressed(MouseEvent me)
    {
        pressed = me;
    }

    @Override
    public void mouseDragged(MouseEvent me)
    {
        dragged = me;
        Component component = dragged.getComponent();
        Container parent = component.getParent();
        Container superParent = parent.getParent();

        if(superParent instanceof JViewport)
        {
            JViewport vp = (JViewport)superParent;
            Rectangle vpb = vp.getBounds();
            Point pt = MouseInfo.getPointerInfo().getLocation();
            SwingUtilities.convertPointFromScreen(pt, vp);

            if(!vpb.contains(pt))
            {
                int yDiff = (pt.y < vpb.y ) ? pt.y : pt.y - vpb.height;
                vpb.translate(0, yDiff);
                vp.scrollRectToVisible(vpb);
            }
        }

        location = component.getLocation(location);
        int x = location.x - pressed.getX() + me.getX();
        int y = location.y - pressed.getY() + me.getY();
        component.setLocation(x, y);
    }

    // Mouse release omitted
}

Gui (Created in NetBeans)

import java.awt.Rectangle;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionAdapter;
import java.awt.event.MouseMotionListener;
import javax.swing.JButton;
import javax.swing.JPanel;

public class DragginTest extends javax.swing.JFrame
{
    public DragginTest()
    {
        initComponents();
        addListeners(jButton1, jButton2, jButton3, jButton4, jButton5, jButton6, jButton7, jButton8, jButton9);
    }

    private void addListeners(JButton... buttons)
    {
        DragListener drag = new DragListener();
        for(JButton b : buttons)
        {
            b.addMouseListener(drag);
            b.addMouseMotionListener(drag); 
        }   
    }

    @SuppressWarnings("unchecked")

    private void initComponents()
    {
        jLayeredPane1 = new javax.swing.JLayeredPane();
        jScrollPane1 = new javax.swing.JScrollPane();
        mainPanel = new javax.swing.JPanel();
        jButton1 = new javax.swing.JButton();
        jButton2 = new javax.swing.JButton();
        jButton3 = new javax.swing.JButton();
        jButton4 = new javax.swing.JButton();
        jButton5 = new javax.swing.JButton();
        jButton6 = new javax.swing.JButton();
        jButton7 = new javax.swing.JButton();
        jButton8 = new javax.swing.JButton();
        jButton9 = new javax.swing.JButton();

        setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
        setPreferredSize(new java.awt.Dimension(450, 450));

        mainPanel.setLayout(new java.awt.GridLayout(5, 2, 2, 2));

        // Below Repeated for buttons 1-9 (left out for conciseness)
        jButton1.setFont(new java.awt.Font("Tahoma", 1, 48)); // NOI18N
        jButton1.setForeground(new java.awt.Color(255, 0, 0));
        jButton1.setText("1");
        mainPanel.add(jButton1);
        // End Repeat

        jScrollPane1.setViewportView(mainPanel);

        javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane());
        getContentPane().setLayout(layout);
        layout.setHorizontalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
        .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
            .addGap(40, 40, 40)
            .addComponent(jScrollPane1, javax.swing.GroupLayout.PREFERRED_SIZE, 205, javax.swing.GroupLayout.PREFERRED_SIZE)
            .addGap(38, 38, 38))
        );
        layout.setVerticalGroup(
        layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
        .addGroup(layout.createSequentialGroup()
            .addGap(40, 40, 40)
            .addComponent(jScrollPane1, javax.swing.GroupLayout.PREFERRED_SIZE, 226, javax.swing.GroupLayout.PREFERRED_SIZE)
            .addContainerGap(53, Short.MAX_VALUE))
        );

        pack();
    }

    public static void main(String args[])
    {
        /* Create and display the form */
        java.awt.EventQueue.invokeLater(new Runnable()
        {
            public void run()
            {
                new DragginTest().setVisible(true);
            }
        });
    }

    private javax.swing.JButton jButton1;
    private javax.swing.JButton jButton2;
    private javax.swing.JButton jButton3;
    private javax.swing.JButton jButton4;
    private javax.swing.JButton jButton5;
    private javax.swing.JButton jButton6;
    private javax.swing.JButton jButton7;
    private javax.swing.JButton jButton8;
    private javax.swing.JButton jButton9;
    private javax.swing.JLayeredPane jLayeredPane1;
    private javax.swing.JScrollPane jScrollPane1;
    private javax.swing.JPanel mainPanel;
}

Solution

  • I added a hack to your DragListener code. Basically it removes the layout manager while you are dragging so the revalidates do nothing and it restores the layout manager when the mouse is released:

    import java.awt.*;
    import java.awt.Container;
    import java.awt.MouseInfo;
    import java.awt.Point;
    import java.awt.Rectangle;
    import java.awt.event.MouseEvent;
    import javax.swing.JViewport;
    import javax.swing.SwingUtilities;
    import javax.swing.event.MouseInputAdapter;
    
    public class DragListener extends MouseInputAdapter
    {
        private Point location;
        private MouseEvent pressed;
        private MouseEvent dragged;
        private MouseEvent dropped;
        private LayoutManager layout;
    
        @Override
        public void mousePressed(MouseEvent me)
        {
            pressed = me;
            Component component = me.getComponent();
            Container parent = component.getParent();
            parent.setPreferredSize(parent.getPreferredSize());
            layout = parent.getLayout();
            parent.setLayout(null);
        }
    
        @Override
        public void mouseDragged(MouseEvent me)
        {
            dragged = me;
            Component component = dragged.getComponent();
            Container parent = component.getParent();
            Container superParent = parent.getParent();
    
            if(superParent instanceof JViewport)
            {
                JViewport vp = (JViewport)superParent;
                Rectangle vpb = vp.getBounds();
                Point pt = MouseInfo.getPointerInfo().getLocation();
                SwingUtilities.convertPointFromScreen(pt, vp);
    
                if(!vpb.contains(pt))
                {
                    int yDiff = (pt.y < vpb.y ) ? pt.y : pt.y - vpb.height;
                    vpb.translate(0, yDiff);
                    vp.scrollRectToVisible(vpb);
                }
            }
    
            location = component.getLocation(location);
            int x = location.x - pressed.getX() + me.getX();
            int y = location.y - pressed.getY() + me.getY();
            component.setLocation(x, y);
        }
    
        // Mouse release omitted
        @Override
        public void mouseReleased(MouseEvent me)
        {
            Component component = me.getComponent();
            Container parent = component.getParent();
            parent.setPreferredSize( null );
            parent.setLayout(layout);
            parent.validate();
            parent.repaint();
        }
    }
    

    Of course I assume your real mouseReleased code will have logic to insert the button into the appropriate place in the Container so its real location can be maintained by the GridLayout, otherwise the component will just go back to its original location.

    Edit:

    Here is a version that moves the button to its new location when the mouse button is released. A little complicated because you need to worry about ZOrder. That is dragging a component down is ok. But if you try to drag a component up, then it gets painted below the other buttons. Temporarily resetting the ZOrder solves this problem.

    Boy the code is beginning to be a big hack:) Temporary null layout and temporary ZOrder.

    Anyway here is the code:

    import java.awt.*;
    import java.awt.Container;
    import java.awt.MouseInfo;
    import java.awt.Point;
    import java.awt.Rectangle;
    import java.awt.event.MouseEvent;
    import javax.swing.*;
    import javax.swing.SwingUtilities;
    import javax.swing.event.MouseInputAdapter;
    
    public class DragListener extends MouseInputAdapter
    {
        private Point location;
        private MouseEvent pressed;
        private MouseEvent dragged;
        private MouseEvent dropped;
        private LayoutManager layout;
        private Rectangle originalBounds;
        private int originalZOrder;
    
        @Override
        public void mousePressed(MouseEvent me)
        {
            pressed = me;
            Component component = me.getComponent();
            Container parent = component.getParent();
            originalBounds = component.getBounds();
            originalZOrder = parent.getComponentZOrder(component);
            parent.setPreferredSize(parent.getPreferredSize());
            layout = parent.getLayout();
            parent.setLayout(null);
            parent.setComponentZOrder(component, 0);
        }
    
        @Override
        public void mouseDragged(MouseEvent me)
        {
            JComponent source = (JComponent) me.getComponent();
            JComponent parent = (JComponent) source.getParent();
    
            Point p = me.getPoint();
            p = SwingUtilities.convertPoint(source, p, parent);
    
            Rectangle bounds = source.getBounds();
            bounds.setLocation(p);
    
            bounds.x -= pressed.getX();
            bounds.y -= pressed.getY();
            source.setLocation(0, bounds.y);
            parent.scrollRectToVisible(bounds);
        }
    
        @Override
        public void mouseReleased(MouseEvent me)
        {
            boolean moved = false;
            Component component = me.getComponent();
            Container parent = component.getParent();
            Point location = component.getLocation();
    
            if (location.y < 0)
            {
                parent.add(component, 0);
                moved = true;
            }
            else
            {
                for (int i = 0; i < parent.getComponentCount(); i++)
                {
                    Component c = parent.getComponent(i);
                    Rectangle bounds = c.getBounds();
    
                    if (c == component)
                        bounds = originalBounds;
    
                    //  Component is released in the space originally occupied
                    //  by the component or over an existing component
    
                    if (bounds.contains(0, location.y))
                    {
                        if (c == component)
                        {
                            parent.setComponentZOrder(component, originalZOrder);
                        }
                        else
                        {
                            parent.add(component, i);
                        }
    
                        moved = true;
                        break;
                    }
                }
            }
    
            //  Component is positioned below all components in the container
    
            if (!moved)
            {
                parent.add(component, parent.getComponentCount() - 1);
            }
    
            //  Restore layout manager
    
            parent.setPreferredSize( null );
            parent.setLayout(layout);
            parent.validate();
            parent.repaint();
            component.requestFocusInWindow();
        }
    
        private static void createAndShowGUI()
        {
            JPanel panel = new JPanel( new GridLayout(0, 1) );
            DragListener drag = new DragListener();
    
            for (int i = 0; i <10; i++)
            {
                JButton button = new JButton("" + i);
                button.setFont(new java.awt.Font("Tahoma", 1, 48));
                button.setForeground(new java.awt.Color(255, 0, 0));
                button.addMouseListener(drag);
                button.addMouseMotionListener(drag);
                panel.add( button );
            }
    
            JFrame frame = new JFrame("SSCCE");
            frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            frame.add( new JScrollPane(panel) );
            frame.setLocationByPlatform( true );
            frame.setSize(200, 400);
            frame.setVisible( true );
        }
    
        public static void main(String[] args)
        {
            EventQueue.invokeLater(new Runnable()
            {
                public void run()
                {
                    createAndShowGUI();
                }
            });
        }
    }