JScrollBars value change not keeping maximum value constant

403 Views Asked by At

So I have the JScrollPane view like so (this is my minimal reproducible example),

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

I set the JScrollPane scroll bars to have new maximum values which can be seen above, but I will type here again. (This is done so I can scroll slower in the same view size (My unit increment of 1 was still scrolling too fast for me))

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

So I start up the program and the jscrollbars have defintely increased in their maximum size, which is what I want. With that, I also have the following code to change the jscrollbars values using a keylistener, which can also be seen above.

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;

Problem:

When I press any of those keys, the scrollbars maximums are no longer what I set them too, and instead have their maximum sizes defaulted to the preferred size of map (I tested this by printing out the maximum sizes of the scrollbars before and after I pressed any of the keys and they changed). I believe this problem derives from the bar.setValue() method, not sure why.

So, how can I prevent the jscrollbars from changing their maximum sizes? My goal is to scroll the entirety of the jlabel map with solely W, A, S, and D keys with my set maximum values to the jscrollbars.

2

There are 2 best solutions below

3
camickr On BEST ANSWER

The JScrollBar uses the DefaultBoundedRangeModel to track the values.

Inside the BasicScrollPaneUI is the method syncScrollPaneWithViewport() which invokes hsb.setValues(...).

The setValues(...) method invokes the setRangeProperties(...) method of the BoundedRangeModel.

Unfortunately the max value is reset based on the width of the panel added to the viewport.

So my suggestion is that you will need to create your own custom BoundedRangeModel and make sure the "max" value is not reset (to a smaller value)? I don't know if this will affect scrolling or not.

Your can check out the source code for the DefaultBoundedRangeModel and BasicScrollPaneUI at:

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

Here is the test code I used to verify that the max value was being changed by a method external to the setValue(...) statement itself. However, this statement does apparently cause the synching of the viewport to happen:

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

        }
}

Edit:

Above code demonstrates how to extend the DefaultBoundedRangeModel. Currently, it only shows when each method is invoked to try to understand the logic flow when the model is updated.

Note that after the "setMaximum" the "setRangeProperties" is invoked with the correct max value.

However, "setRangeProperties" is invoked a second time (as a result of logic found in the BasicScrollPaneUI as I tried to explain at the top of my answer). Only this time it is invoked with the width of the panel (1200) as the maximum value, not the 8000 you originally specified.

So it would appear you need to rewrite the code of the setRangeProperties(...) method to always use the 8000 as the maximum value. So maybe when you create an instance of the MyBoundedRangeModel you pass in the maximum value you want to use.

I gave you a link where you can find the source code for the DefaultBoundedRangeModel. Start by copying the code from the setRangeProperties(...) method to use as a starting point. Then you need to customize the code to make sure the maximum is always 8000.

This is my best guess as to how you can solve the problem. I don't know if it will work or not.

2
Gilbert Le Blanc On

I finally got your example to work somewhat. I can move the scroll bars with the WASD keys.

I removed the static from the JScrollPane class field.

I started the Swing application with a call to the SwingUtilities invokeLater method. This method ensures that the Swing components are created and executed on the Event Dispatch Thread.

I added the key listener to the JScrollPane, rather than the JFrame.

I made the JScrollPane focusable, so the key presses would actually register. Key bindings would work a lot easier.

Here's the complete runnable code.

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