繁体   English   中英

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

[英]Scroll JScrollPane by dragging mouse (Java swing)

我正在为我正在开发的游戏制作 map 编辑器。 JScrollPane 中有一个 JPanel,显示要编辑的 map。 我想做的是,当用户按住空格键并在 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);
            }
        }

});

目前它可以工作,但滚动一点也不流畅。 一次移动鼠标很多是好的,但是做小的拖动会使滚动窗格 go 狂暴。

任何想法如何改善这一点?

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

地图编辑器

附加说明(编辑):

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

好吧,那最终要简单得多,然后我认为它会是......

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

下面的示例只是计算用户单击的点与他们拖动的量之间的差异。 然后将此增量应用于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;
}

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

我希望它有帮助!

我提出了如下解决方案(上面的方法对我不起作用,JDK 1.8):

  1. 将 MouseDragged 事件附加到您的 JScrollPane;
  2. 事件 function 在拖动开始和结束时被触发两次;
  3. 您需要一个全局变量来存储初始鼠标指针 position(xS 和 yS);
  4. 这是 MouseDragged 的代码:
yourJScrollPaneMouseDragged(java.awt.event.MouseEvent evt) {                                        
        Rectangle view = yourJScrollPane.getVisibleRect();
        if (xS == 0 && yS == 0) { // first time event fired, store the initial mouse position
            xS = evt.getX();
            yS = evt.getY();
        } else {                  // second time event fired - actual scrolling
            int speed = 20;
            view.x += Integer.signum(xS - evt.getX()) * speed;
            view.y += Integer.signum(yS - evt.getY()) * speed;
                  // The view is scrolled by constant value of 20.
                  // For some reason, periodically, second position values were off for me by alot,
                  // which caused unwanted jumps.
                  // Integer.signum gets the direction the movement was performed.
                  // You can ommit the signum and constant and
                  // check if it works for you without jagging.

            yourJScrollPane.getViewport().scrollRectToVisible(view);
              // you actually have to fire scrollRectToVisible with the child 
              // component within JScrollPane, Viewport is the top child
            
            // reset globals:
            xS = 0;
            yS = 0;
        }
}      

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM