JScrollBars 值更改不保持最大值不变

JScrollBars value change not keeping maximum value constant

所以我有这样的 JScrollPane view(这是我最小的可重现示例),

import javax.swing.*;
import java.awt.event.*;
import java.awt.*;

public class Test implements KeyListener {
           private static JScrollPane view;   

           public Test() { 
               create();
           }

           public static void main(String[] args) {
               new Test();
           }

           public void create() {
              JFrame frame = new JFrame(); //make frame
              frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
              frame.setSize(1000, 1000);
              frame.setLocationRelativeTo(null);
              frame.setResizable(false);

              SpringLayout layout = new SpringLayout(); 
              JPanel base = new JPanel();
              base.setPreferredSize(new Dimension(1000, 1000));
              base.setLayout(layout);
                
              JPanel map = new JPanel();  //make panel for the scrollpane
              map.setPreferredSize(new Dimension(1000, 1000));
              
              
              view = new JScrollPane(map);  //initialize scrollpane and add to base with a SpringLayout
              view.setPreferredSize(new Dimension(300, 300));
              layout.putConstraint(SpringLayout.HORIZONTAL_CENTER, view, 0, SpringLayout.HORIZONTAL_CENTER, base);
              layout.putConstraint(SpringLayout.VERTICAL_CENTER, view, 0,  SpringLayout.VERTICAL_CENTER, base);
              base.add(view); //add scrollpane to base jpanel

              frame.add(base);
              frame.setVisible(true);
              frame.addKeyListener(this);
              
              view.getHorizontalScrollBar().setMaximum(10*800); //set wanted max sizes of each scroll bar
              view.getVerticalScrollBar().setMaximum(10*800);
           }

           public void keyPressed(KeyEvent event) {  //W, A, S, D keys to change values of jscrollbars
               switch(event.getKeyCode()) {
              case KeyEvent.VK_W:
                   view.getVerticalScrollBar().setValue((int) (view.getVerticalScrollBar().getValue() - 10*0.9));
                   break;
              case KeyEvent.VK_S:
                   view.getVerticalScrollBar().setValue((int) (view.getVerticalScrollBar().getValue() + 10*0.9));
                   break;
              case KeyEvent.VK_A:
                   view.getHorizontalScrollBar().setValue((int) (view.getHorizontalScrollBar().getValue() - 10*0.9));
                   break;
              case KeyEvent.VK_D:
                   view.getHorizontalScrollBar().setValue((int) (view.getHorizontalScrollBar().getValue() + 10*0.9));
                   break;
               }
           }

        @Override
        public void keyTyped(KeyEvent e) {
            // TODO Auto-generated method stub
            
        }

        @Override
        public void keyReleased(KeyEvent e) {
            // TODO Auto-generated method stub
            
        }
}

我将 JScrollPane 滚动条设置为具有新的最大值,可以在上面看到,但我会在这里再次输入。 (这样做是为了让我可以在相同的视图大小下滚动得更慢(我的单位增量 1 对我来说仍然滚动得太快了))

view.getHorizontalScrollBar().setMaximum(10*800);
view.getVerticalScrollBar().setMaximum(10*800);

所以我启动了程序,jscrollbars 的最大尺寸明显增加了,这正是我想要的。有了它,我还有以下代码来使用 keylistener 更改 jscrollbars 值,这也可以在上面看到。

case KeyEvent.VK_W:
    view.getVerticalScrollBar().setValue((int) (view.getVerticalScrollBar().getValue() - 10*0.3));
    break;
case KeyEvent.VK_S:
    view.getVerticalScrollBar().setValue((int) (view.getVerticalScrollBar().getValue() + 10*0.3));
    break;
case KeyEvent.VK_A:
    view.getHorizontalScrollBar().setValue((int) (view.getHorizontalScrollBar().getValue() - 10*0.3));
    break;
case KeyEvent.VK_D:
    view.getHorizontalScrollBar().setValue((int) (view.getHorizontalScrollBar().getValue() + 10*0.3));
    break;

问题:

当我按下这些键中的任何一个时,滚动条的最大值也不再是我设置的,而是将它们的最大尺寸默认为 map 的首选尺寸(我通过打印出在我按下任何键之前和之后滚动条的最大尺寸,它们发生了变化)。我相信这个问题源于 bar.setValue() 方法,不知道为什么。

那么,我怎样才能防止 jscrollbars 改变它们的最大尺寸呢?我的目标是仅使用 W、A、S 和 D 键将整个 jlabel map 滚动,并将我设置的最大值滚动到 jscrollbars。

我终于让你的例子有点用了。我可以用 WASD 键移动滚动条。

我从 JScrollPane class 字段中删除了静态。

我通过调用 SwingUtilities invokeLater 方法启动了 Swing 应用程序。此方法确保 Swing 组件在 Event Dispatch Thread.

上创建和执行

我将关键侦听器添加到 JScrollPane,而不是 JFrame

我使 JScrollPane 可聚焦,因此按键实际上会注册。 Key bindings 工作起来会容易得多。

这是完整的可运行代码。

import java.awt.Dimension;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;

import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.SpringLayout;
import javax.swing.SwingUtilities;

public class JScrollPaneTest implements KeyListener {
    private JScrollPane view;   

    public JScrollPaneTest() { 
        create();
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                 new JScrollPaneTest();
            }
        });
    }

    public void create() {
       JFrame frame = new JFrame(); //make frame
       frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
       frame.setSize(1000, 1000);
       frame.setLocationRelativeTo(null);
       frame.setResizable(false);

       SpringLayout layout = new SpringLayout(); 
       JPanel base = new JPanel();
       base.setPreferredSize(new Dimension(1000, 1000));
       base.setLayout(layout);
         
       JPanel map = new JPanel();  //make panel for the scrollpane
       map.setPreferredSize(new Dimension(1000, 1000));
       
       
       view = new JScrollPane(map);  //initialize scrollpane and add to base with a SpringLayout
       view.setFocusable(true);
       view.setPreferredSize(new Dimension(300, 300));
       layout.putConstraint(SpringLayout.HORIZONTAL_CENTER, view, 0, SpringLayout.HORIZONTAL_CENTER, base);
       layout.putConstraint(SpringLayout.VERTICAL_CENTER, view, 0,  SpringLayout.VERTICAL_CENTER, base);
       base.add(view); //add scrollpane to base jpanel

       frame.add(base);
       frame.setVisible(true);
       view.addKeyListener(this);
       
       view.getHorizontalScrollBar().setMaximum(10*800); //set wanted max sizes of each scroll bar
       view.getVerticalScrollBar().setMaximum(10*800);
    }

    public void keyPressed(KeyEvent event) { // W, A, S, D keys to change values of jscrollbars
        int verticalValue = view.getVerticalScrollBar().getValue();
        int horizontalValue = view.getHorizontalScrollBar().getValue();
        
        switch (event.getKeyCode()) {
        case KeyEvent.VK_W:
            view.getVerticalScrollBar().setValue(verticalValue - 10);
            break;
        case KeyEvent.VK_S:
            view.getVerticalScrollBar().setValue(verticalValue + 10);
            break;
        case KeyEvent.VK_A:
            view.getHorizontalScrollBar().setValue(horizontalValue - 10);
            break;
        case KeyEvent.VK_D:
            view.getHorizontalScrollBar().setValue(horizontalValue + 10);
            break;
        }
    }

    @Override
    public void keyTyped(KeyEvent e) {
    }

    @Override
    public void keyReleased(KeyEvent e) {
    }
 
}

JScrollBar 使用 DefaultBoundedRangeModel 来跟踪值。

BasicScrollPaneUI 内部是调用 hsb.setValues(...).

的方法 syncScrollPaneWithViewport()

setValues(...) 方法调用 BoundedRangeModel 的 setRangeProperties(...) 方法。

不幸的是,最大值是根据添加到视口的面板宽度重置的。

所以我的建议是您需要创建自己的自定义 BoundedRangeModel 并确保“最大”值未重置(为较小的值)?不知道会不会影响滚动

您可以在以下位置查看 DefaultBoundedRangeModel 和 BasicScrollPaneUI 的源代码:

https://github.com/openjdk/jdk/tree/master/src/java.desktop/share/classes/javax

这是我用来验证最大值是否被 setValue(...) 语句本身外部的方法更改的测试代码。但是,此语句显然会导致视口同步发生:

import javax.swing.*;
import java.awt.event.*;
import java.awt.*;

public class Test implements KeyListener {
           private static JScrollPane view;

           public Test() {
               create();
           }

           public static void main(String[] args) {
                SwingUtilities.invokeLater( () -> new Test() );
           }

           public void create() {
              JFrame frame = new JFrame(); //make frame
              frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
              frame.setSize(1000, 1000);
              frame.setLocationRelativeTo(null);
              frame.setResizable(false);

              SpringLayout layout = new SpringLayout();
              JPanel base = new JPanel();
              base.setPreferredSize(new Dimension(1000, 1000));
              base.setLayout(layout);

              JPanel map = new JPanel();  //make panel for the scrollpane
              map.setPreferredSize(new Dimension(1200, 1200));


              view = new JScrollPane(map);  //initialize scrollpane and add to base with a SpringLayout
              view.setPreferredSize(new Dimension(300, 300));
              layout.putConstraint(SpringLayout.HORIZONTAL_CENTER, view, 0, SpringLayout.HORIZONTAL_CENTER, base);
              layout.putConstraint(SpringLayout.VERTICAL_CENTER, view, 0,  SpringLayout.VERTICAL_CENTER, base);
              base.add(view); //add scrollpane to base jpanel

                view.getHorizontalScrollBar().setModel( new MyBoundedRangeModel() );
                System.out.println("default value");
                System.out.println(view.getHorizontalScrollBar().getMaximum());


              frame.add(base);
              frame.setVisible(true);
              frame.addKeyListener(this);

                System.out.println("after Visible");
                System.out.println(view.getHorizontalScrollBar().getMaximum());


               view.getHorizontalScrollBar().setMaximum(10*800); //set wanted max sizes of each scroll bar
               view.getVerticalScrollBar().setMaximum(10*800);

                System.out.println("after changing maximum");
                System.out.println(view.getHorizontalScrollBar().getMaximum());


           }

           public void keyPressed(KeyEvent event)
           {
                System.out.println("Before");
                System.out.println(view.getHorizontalScrollBar().getValue());
                System.out.println(view.getHorizontalScrollBar().getMaximum());

               switch(event.getKeyCode()) {
              case KeyEvent.VK_W:
                   view.getVerticalScrollBar().setValue((int) (view.getVerticalScrollBar().getValue() - 10*0.9));
                   break;
              case KeyEvent.VK_S:
                   view.getVerticalScrollBar().setValue((int) (view.getVerticalScrollBar().getValue() + 10*0.9));
                   break;
              case KeyEvent.VK_A:
                   view.getHorizontalScrollBar().setValue((int) (view.getHorizontalScrollBar().getValue() - 10*0.9));
                   break;
              case KeyEvent.VK_D:
                   view.getHorizontalScrollBar().setValue((int) (view.getHorizontalScrollBar().getValue() + 10*0.9));
                   break;
               }

                System.out.println("after");
                System.out.println(view.getHorizontalScrollBar().getValue());
                System.out.println(view.getHorizontalScrollBar().getMaximum());
           }

        @Override
        public void keyTyped(KeyEvent e) {
            // TODO Auto-generated method stub

        }

        @Override
        public void keyReleased(KeyEvent e) {
            // TODO Auto-generated method stub

        }

        class MyBoundedRangeModel extends DefaultBoundedRangeModel
        {
            @Override
            public void setValue(int i)
            {
                System.out.println("setValue");
                super.setValue(i);
            }

            @Override
            public void setMaximum(int i)
            {
                System.out.println("setMaximum");
                super.setMaximum(i);
            }
            @Override
            public void setMinimum(int i)
            {
                System.out.println("setMinimum");
                super.setMinimum(i);
            }

            @Override
            public void setExtent(int i)
            {
                System.out.println("setExtent");
                super.setExtent(i);
            }

            @Override
            public void setRangeProperties(int newValue, int newExtent, int newMin, int newMax, boolean adjusting)
            {
                System.out.println("setRangeProperties");
                System.out.println(newValue + " : " + newExtent + " : " + newMax);
                super.setRangeProperties(newValue, newExtent, newMin, newMax, adjusting);
            }

        }
}

编辑:

以上代码演示了如何扩展 DefaultBoundedRangeModel。目前,它仅显示何时调用每个方法以尝试了解模型更新时的逻辑流程。

请注意,在“setMaximum”之后,将使用正确的最大值调用“setRangeProperties”。

然而,“setRangeProperties”被第二次调用(由于我在答案顶部试图解释的 BasicScrollPaneUI 中发现的逻辑)。只有这一次它被调用时面板的宽度 (1200) 作为最大值,而不是您最初指定的 8000。

所以看起来您需要重写 setRangeProperties(...) 方法的代码以始终使用 8000 作为最大值。因此,也许当您创建 MyBoundedRangeModel 的实例时,您会传入要使用的最大值。

我给了你一个link,你可以在其中找到 DefaultBoundedRangeModel 的源代码。首先从 setRangeProperties(...) 方法复制代码以用作起点。然后您需要自定义代码以确保最大值始终为 8000。

这是我对您如何解决问题的最佳猜测。不知道行不行。