1
votes

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...).

1
I would expect the thumb to be reversed as well for vertical... - it is not an inconsistency. The component orientation is right-to-left, not down-to-up. This is because of how languages work. Some languages are read left-to-right and top-to-bottom. Others are right-to-left and top-to-bottom. Bottom-to-top is not supported. - camickr
when the scrollbar value is at the model minimum. - how are you setting the value of the model? Maybe you can override the setValue(...) method of the scrollbar to calculate the value as: maxValue - value and then invoke super.setValue(...) with the result of the calculation. Or maybe you can override the getMin/Max value methods to return the opposite. I'm not really sure I understand the usage of this feature. - camickr
@camickr I was hoping there was an unknown-to-me property to set somewhere but I might resort to overriding the get/setValue() methods. I'm setting the value through getModel().setRangeProperties() so I might also be able to provide a different type of BoundedRangeModel? See my edit for a use-case. - Matthieu
An interesting question, although I think that relying on the component orientation may not be appropriate here. The scroll bar is a view for a BoundedRangeModel. This model enforces the constraint minimum <= value <= value+extent <= maximum, so for some "simple model-based workarounds", you're out of luck. But considering that the scroll bar is also just a view from your application's perspective, couldn't the mapping from a range like [0,10] to the range [10,0] be done in your application, at the point where these values are actually used? - Marco13
@Marco13 indeed that's where I was heading and will probably end up doing. I actually started with going from [0,10] to [-10,0] and checking getValue() < 0, but it started to get dirty at some point, probably due to a late evening coding though... ;) - Matthieu

1 Answers

0
votes

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.