Search code examples
javaswinguser-interfacejscrollbar

Switch vertical JScrollBar min/max location


I have a vertical JScrollBar which default behaviour is to show the thumb at the top when the scrollbar value is at the model minimum.

I'd like to have the opposite behaviour: the thumb would be at the bottom when the value is at the minimum.

I have tried setComponentOrientation(ComponentOrientation.RIGHT_TO_LEFT) and it works for horizontal scrollbars but not for vertical ones...

How can I get it to work as expected (without creating a full ScrollBarUI if possible)? Is that expected behaviour (a kind of "UI inconsistency"* between horizontal and vertical scrollbars, or a missing ComponentOrientation.BOTTOM_TO_TOP constant)?

Here is some SSCE that illustrate the problem:

public static void main(String[] args) throws Exception {
    JFrame f = new JFrame();
    f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    int orient = JScrollBar.VERTICAL; // <<--- Change to HORIZONTAL
    JScrollBar sb = new JScrollBar(orient, 0, 100, 0, 1000);
    sb.setComponentOrientation(ComponentOrientation.RIGHT_TO_LEFT); // <<--- Works only for HORIZONTAL
    sb.addAdjustmentListener(new AdjustmentListener() {
        @Override public void adjustmentValueChanged(AdjustmentEvent e) {
            System.out.println(e.getValue());
        }
    });
    f.add(sb);
    if (orient == JScrollBar.HORIZONTAL)
        f.setBounds(100, 100, 500, 100);
    else
        f.setBounds(100, 100, 100, 500);
    f.setVisible(true);
}

*: "inconsistency" because if it reverses the thumb for horizontal, I would expect the thumb to be reversed as well for vertical...

EDIT: The scrollbar is used to control scrolling in a map that can be configured to have its origin at the top-or-bottom of the screen, going down-or-up, or at the left-or-right, going right-or-left (it has to match a physical setup, so is configurable). Handling the left-or-right setup is just setting the scrollbar to horizontal, with right-to-left orientation, but the same is not working for vertical.

I also checked the source of BasicScrollBarUI and it indeed handles the getComponentOrientation().isLeftToRight() only when the scrollbar is HORIZONTAL. So it is indeed expected behaviour (just not the one I expected...).


Solution

  • I ended up subclassing JScrollBar with a getAdjustedValue() that does the translation. The idea is to "reverse" min and max, so [min;max] becomes [-max;-min]. Then we have to take the extent into account when adjusting the model value, as the bottom of the thumb (value + extent) is the value we want:

    public static class MySB extends JScrollBar {
        private boolean inv;
        public MySB(int orient, int value, int extent, int min, int max, boolean inv) {
            super(orient, inv ? -value-extent : value, extent, inv ? -max : min, inv ? -min : max);
            this.inv = inv;
        }
        public int getAdjustedValue() {
            int v = super.getValue();
            if (inv)
                v = -(v + model.getExtent());
            return v;
        }
    }
    

    Then modified the SSCE above, adding sb.getAdjustedValue() in the AdjustmentListener:

    ...
    MySB sb = new MySB(orient, 0, 100, 0, 1000, true);
    sb.addAdjustmentListener(new AdjustmentListener() {
        @Override
        public void adjustmentValueChanged(AdjustmentEvent e) {
            System.out.println(e.getValue()+", "+((MySB)e.getAdjustable()).getMyValue());
        }
    });
    ...
    

    Scrollbar

    -100, 0
    -200, 100
    -200, 100
    -200, 100
    -300, 200
    -300, 200
    

    We see the original value, and the corrected one. I initially started subclassing the model and overriding getValue() but it messed up the page up/down and arrows behaviour.