提问者:小点点

通过拖动鼠标滚动JScrollPane(Java swing)


我正在为我正在制作的游戏制作地图编辑器。JScroll窗格中有一个 JPanel,用于显示要编辑的地图。我想做的是,当用户按住空格键并在 JPanel 中拖动鼠标时,JScrollPanel 将随着拖动一起滚动。这是我到目前为止所拥有的:

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);
            }
        }

});

目前它可以工作,但滚动根本不流畅。一次移动鼠标很多是可以的,但做小的拖动会使滚动窗格变得疯狂。

有什么想法可以改善这一点吗?

对于那些喜欢视觉帮助的人,这里是编辑器:

补充说明(编辑):

    我试过scrollPane.getViewport().setViewPosition(new Point(scrollPane.getViewport().getViewPosition().
  • x deltaX, scrollPane.getViewport().getViewPosition().y deltaY));
  • 缓慢移动鼠标时拖动更烦躁,而大动作更流畅
  • 我尝试使用scrollRectToVisible没有运气

共3个答案

匿名用户

好吧,那最终比我简单得多,尽管它会......

首先,不要弄乱 JViewport,而是直接在充当 JScrollPane 内容的组件上使用 JComponent#scrollRectToVisibleMouseListener 应该附加到该组件上。

下面的示例只是计算用户单击的点与他们拖动的量之间的差值。然后,它将此增量应用于 JViewportviewRect,并使用 JComponent#scrollRectToVisible 来更新可视区域,简单:)

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);
        }

    }

}

匿名用户

我发现这个(非常常见的)要求出奇地难以解决。这是我们在生产中可能已经超过10年的稳定解决方案。

接受的答案似乎非常诱人,但一旦你开始使用它就会出现可用性故障(例如,尝试立即拖动到右下角然后向后拖动,你应该注意到在向后移动的过程中,长时间没有移动发生)。

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);
  }
}

匿名用户

我目前正在自己开发地图编辑器。我已经让鼠标滚动在我的手机上顺利工作,尽管这是一个非常冗长的解决方案。

我编写了两个自定义AWTEventListeners,一个用于鼠标事件,另一个用于鼠标移动事件。我这样做是因为我的地图是一个自定义的 JComponent,因此不会填充整个视口。这意味着,如果光标位于组件上,则不会检测到滚动窗格鼠标事件。

对我来说,这工作非常顺利,内容与鼠标光标完美地同步滚动。

(我应该提到我使用鼠标滚轮单击而不是空格键,但它很容易更改)。

    Toolkit.getDefaultToolkit().addAWTEventListener(new AWTEventListener() {
        public void eventDispatched(AWTEvent event) {
            if(event instanceof MouseEvent){
                MouseEvent e = (MouseEvent)event;
                //Begin a scroll if mouse is clicked on our pane
                if(isMouseInMapPane()){
                    if(e.getID() == MouseEvent.MOUSE_PRESSED){
                        if(e.getButton() == MouseEvent.BUTTON2){
                            mouseWheelDown = true;
                            currentX = MouseInfo.getPointerInfo().getLocation().x;
                            currentY = MouseInfo.getPointerInfo().getLocation().y;
                        }
                    }
                }
                //Stop the scroll if mouse is released ANYWHERE
                if(e.getID() == MouseEvent.MOUSE_RELEASED){
                    if(e.getButton() == MouseEvent.BUTTON2){
                        mouseWheelDown = false;
                    }
                }
            }
        }
    }, AWTEvent.MOUSE_EVENT_MASK);

    Toolkit.getDefaultToolkit().addAWTEventListener(new AWTEventListener() {
        public void eventDispatched(AWTEvent event) {
            if(event instanceof MouseEvent){
                MouseEvent e = (MouseEvent)event;

                //Update the scroll based on delta drag value
                if(e.getID() == MouseEvent.MOUSE_DRAGGED){
                    if(mouseWheelDown){
                        int newX = MouseInfo.getPointerInfo().getLocation().x;
                        int newY = MouseInfo.getPointerInfo().getLocation().y;
                        int scrollStepX = (currentX - newX);
                        int scrollStepY = (currentY - newY);
                        currentX = newX;
                        currentY = newY;

                        //mapScroll is the reference to JScrollPane
                        int originalValX = mapScroll.getHorizontalScrollBar().getValue();
                        mapScroll.getHorizontalScrollBar().setValue(originalValX + scrollStepX);

                        int originalValY = mapScroll.getVerticalScrollBar().getValue();
                        mapScroll.getVerticalScrollBar().setValue(originalValY + scrollStepY);
                    }
                }

            }
        }
    }, AWTEvent.MOUSE_MOTION_EVENT_MASK);

这是 isMouseInPane 方法:

    private boolean isMouseInMapPane(){
    //Note: mapPane does not need to be your scroll pane.
    //it can be an encapsulating container as long as it is in
    //the same position and the same width/height as your scrollPane.
    //For me I used the JPanel containing my scroll pane.
    Rectangle paneBounds = mapPane.getBounds();
    paneBounds.setLocation(mapPane.getLocationOnScreen());
    boolean inside = paneBounds.contains(MouseInfo.getPointerInfo().getLocation());

    return inside;
}

此代码可以放置在您有权访问滚动窗格引用的任何位置,也可以创建自定义滚动窗格类并将其添加到其中。

我希望它有所帮助!