Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Scroll JScrollPane by dragging mouse (Java swing)

I am making a map editor for a game I am working on. There is a JPanel in the JScrollPane that displays the map to be edited. What I would like to do is make it that when the user is holding down the Spacebar and dragging their mouse in the JPanel, the JScrollPanel will scroll along with the dragging. Here is what I have so far:

panelMapPanel.addMouseMotionListener(new MouseMotionListener(){

        @Override
        public void mouseDragged(MouseEvent e) {
            //Gets difference in distance x and y from last time this listener was called
            int deltaX = mouseX - e.getX();
            int deltaY = mouseY - e.getY();
            mouseX = e.getX();
            mouseY = e.getY();
            if(spacePressed){
                //Scroll the scrollpane according to the distance travelled
                scrollPane.getVerticalScrollBar().setValue(scrollPane.getVerticalScrollBar().getValue() + deltaY);
                scrollPane.getHorizontalScrollBar().setValue(scrollPane.getHorizontalScrollBar().getValue() + deltaX);
            }
        }

});

Currently it works but the scrolling is not smooth at all. Moving the mouse a lot at a time is fine but doing small drags makes the scrollpane go berserk.

Any ideas how to improve this?

For those who enjoy a visual to help, here is the editor:

Map Editor

Addition Notes (Edit):

  • I have tried scrollPane.getViewport().setViewPosition(new Point(scrollPane.getViewport().getViewPosition().x + deltaX, scrollPane.getViewport().getViewPosition().y + deltaY));
  • The dragging is more fidgety when moving the mouse slowly, while big movements are more smooth
  • I tried using scrollRectToVisible without luck
like image 928
vedi0boy Avatar asked Jul 01 '15 20:07

vedi0boy


People also ask

What are the differences between a scrollbar and an JScrollPane in Java Swing?

What is the difference between a Scrollbar and a JScrollPane ? A Scrollbar is a Component, but not a Container. A ScrollPane is a Container. A ScrollPane handles its own events and performs its own scrolling.

What is JScrollPane in Java Swing?

A JScrollPane provides a scrollable view of a component. When screen real estate is limited, use a scroll pane to display a component that is large or one whose size can change dynamically. Other containers used to save screen space include split panes and tabbed panes. The code to create a scroll pane can be minimal.

How do I make my JScrollPane scroll faster?

Just use the reference to your JScrollPane object, get the vertical scroll bar from it using getVerticalScrollBar , and then call setUnitIncrement on it, like this: myJScrollPane. getVerticalScrollBar(). setUnitIncrement(16);


2 Answers

I found this (very common) requirement surprisingly hard to solve. This is the stable solution we have had in production for probably over 10 years.

The accepted answer seems very tempting, but has usability glitches once you start to play with it (e.g. try to immediately drag to the lower right and then back, and you should notice that during the backward movement, no moving takes places for a long time).

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.Point;
import java.awt.event.MouseEvent;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JViewport;
import javax.swing.border.MatteBorder;
import javax.swing.event.MouseInputAdapter;

public class Mover extends MouseInputAdapter {
  public static void main(String[] args) {
    JFrame f = new JFrame();
    f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    f.setSize(200, 160);
    f.setLocationRelativeTo(null);
    f.setLayout(new BorderLayout());

    JScrollPane scrollPane = new JScrollPane();
    f.add(scrollPane, BorderLayout.CENTER);

    JPanel view = new JPanel();
    view.add(new JLabel("Some text"));
    view.setBorder(new MatteBorder(5, 5, 5, 5, Color.BLUE));
    view.setBackground(Color.WHITE);
    view.setPreferredSize(new Dimension(230, 200));
    new Mover(view);
    scrollPane.setViewportView(view);

    f.setVisible(true);
  }

  private JComponent m_view            = null;
  private Point      m_holdPointOnView = null;

  public Mover(JComponent view) {
    m_view = view;
    m_view.addMouseListener(this);
    m_view.addMouseMotionListener(this);
  }

  @Override
  public void mousePressed(MouseEvent e) {
    m_view.setCursor(Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR));
    m_holdPointOnView = e.getPoint();
  }

  @Override
  public void mouseReleased(MouseEvent e) {
    m_view.setCursor(null);
  }

  @Override
  public void mouseDragged(MouseEvent e) {
    Point dragEventPoint = e.getPoint();
    JViewport viewport = (JViewport) m_view.getParent();
    Point viewPos = viewport.getViewPosition();
    int maxViewPosX = m_view.getWidth() - viewport.getWidth();
    int maxViewPosY = m_view.getHeight() - viewport.getHeight();

    if(m_view.getWidth() > viewport.getWidth()) {
      viewPos.x -= dragEventPoint.x - m_holdPointOnView.x;

      if(viewPos.x < 0) {
        viewPos.x = 0;
        m_holdPointOnView.x = dragEventPoint.x;
      }

      if(viewPos.x > maxViewPosX) {
        viewPos.x = maxViewPosX;
        m_holdPointOnView.x = dragEventPoint.x;
      }
    }

    if(m_view.getHeight() > viewport.getHeight()) {
      viewPos.y -= dragEventPoint.y - m_holdPointOnView.y;

      if(viewPos.y < 0) {
        viewPos.y = 0;
        m_holdPointOnView.y = dragEventPoint.y;
      }

      if(viewPos.y > maxViewPosY) {
        viewPos.y = maxViewPosY;
        m_holdPointOnView.y = dragEventPoint.y;
      }
    }

    viewport.setViewPosition(viewPos);
  }
}

demo

like image 143
Reto Höhener Avatar answered Sep 19 '22 07:09

Reto Höhener


Okay, that ended up been much simpler then I though it would be...

First, don't mess with the JViewport, instead, use JComponent#scrollRectToVisible directly on the component which is acting as the contents of the JScrollPane, onto which the MouseListener should be attached.

The following example simply calculates the difference between the point at which the user clicked and the amount they have dragged. It then applies this delta to the JViewport's viewRect and uses JComponent#scrollRectToVisible to update the viewable area, simple :)

enter image description here

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 JLabel map;

        public TestPane() {
            setLayout(new BorderLayout());
            try {
                map = new JLabel(new ImageIcon(ImageIO.read(new File("c:/treasuremap.jpg"))));
                map.setAutoscrolls(true);
                add(new JScrollPane(map));

                MouseAdapter ma = new MouseAdapter() {

                    private Point origin;

                    @Override
                    public void mousePressed(MouseEvent e) {
                        origin = new Point(e.getPoint());
                    }

                    @Override
                    public void mouseReleased(MouseEvent e) {
                    }

                    @Override
                    public void mouseDragged(MouseEvent e) {
                        if (origin != null) {
                            JViewport viewPort = (JViewport) SwingUtilities.getAncestorOfClass(JViewport.class, map);
                            if (viewPort != null) {
                                int deltaX = origin.x - e.getX();
                                int deltaY = origin.y - e.getY();

                                Rectangle view = viewPort.getViewRect();
                                view.x += deltaX;
                                view.y += deltaY;

                                map.scrollRectToVisible(view);
                            }
                        }
                    }

                };

                map.addMouseListener(ma);
                map.addMouseMotionListener(ma);
            } catch (IOException ex) {
                ex.printStackTrace();
            }
        }

        @Override
        public Dimension getPreferredSize() {
            return new Dimension(200, 200);
        }

    }

}
like image 21
MadProgrammer Avatar answered Sep 21 '22 07:09

MadProgrammer