1

Triggered by a question about Slider and a slight bug, I tried to implement a SliderSkin that makes use of axis services, in particular its conversion methods between pixel and value. Works fine except that NumberAxis keeps its conversion offset and scale factor as internal fields that are updated only during a layout pass.

Which poses a problem, if I want to use the conversion during a layout pulse for updating another collaborator: in the case of a Slider that would be the thumb.

Below is a small examples to demonstrate the problem: simply a NumberAxis and a CheckBox at some value. On startup, the box is placed at the value at the middle. For maximal effect, maximize the window and note that the box position is not changed - now sitting near the beginning of the axis. Actually, it's the same when resizing the window but not so visible - see the printout of the difference.

Options to make it work

  • delay the thumb positioning until the next layout pulse (feels clumsy)
  • force the axis to update immediately

Looking for a way for the latter (couldn't find anything except reflectively invoke the axis' layoutChildren).

The example:

public class AxisInvalidate extends Application {

    public static class AxisInRegion extends Region {
        NumberAxis axis;
        Control thumb;
        IntegerProperty value = new SimpleIntegerProperty(50);

        private double thumbWidth;
        private double thumbHeight;

        public AxisInRegion() {
            axis = new NumberAxis(0, 100, 25);
            thumb = new CheckBox();
            getChildren().addAll(axis, thumb);
        }

        @Override
        protected void layoutChildren() {
            thumbWidth = snapSize(thumb.prefWidth(-1));
            thumbHeight = snapSize(thumb.prefHeight(-1));
            thumb.resize(thumbWidth, thumbHeight);

            double axisHeight = axis.prefHeight(-1);
            axis.resizeRelocate(0, getHeight() /4, getWidth(), axisHeight);
            // this marks the layout as dirty but doesn't immediately update internals
            // doesn't make a difference, shouldn't be needed anyway
            //axis.requestAxisLayout();

            double pixelOnAxis = axis.getDisplayPosition(value.getValue());
            Platform.runLater(() -> {
                LOG.info("diff " + (pixelOnAxis - axis.getDisplayPosition(value.getValue())));
            });
            // moving this line into the runlater "solves" the problem  
            thumb.relocate(pixelOnAxis, getHeight() /4);

        }

    }

    private Parent getContent() {
        AxisInRegion region = new AxisInRegion();
        BorderPane content = new BorderPane(region);
        return content;
    }

    @Override
    public void start(Stage primaryStage) throws Exception {
        primaryStage.setScene(new Scene(getContent(), 500, 200));
        primaryStage.show();
    }

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

    @SuppressWarnings("unused")
    private static final Logger LOG = Logger.getLogger(AxisInvalidate.class
            .getName());
}

Actually, I think it's a bug: the value/pixel conversion is a public service of the Axis - that should work always.

Community
  • 1
  • 1
kleopatra
  • 51,061
  • 28
  • 99
  • 211

2 Answers2

1

Not sure if it helps for your initial issue but simply do the calculation manually. Another plus - request layout becomes needless and can be removed.

  double range = axis.getUpperBound()-axis.getLowerBound();
  double pixelOnAxis = axis.getWidth()*(value.get()-axis.getLowerBound())/range;
wzberger
  • 923
  • 6
  • 15
  • certainly another viable option, thanks :-) Would prefer to avoid doing anything manually, though. – kleopatra Dec 07 '15 at 22:35
  • Actually, I would regard that "plus" (doesn't make a difference in this case anyway, just added because core code often adds it) a bug: public services must be provided always and always correctly ;-) – kleopatra Dec 08 '15 at 09:16
1

Just got a working hack from the bug report: we need to call axis.layout() after changing size/location and before querying the conversion methods, something like:

axis.resizeRelocate(0, getHeight() /4, getWidth(), axisHeight);
// doesn't make a difference, shouldn't be needed anyway
//axis.requestAxisLayout();
// working hack from bug report:
axis.layout();
double pixelOnAxis = axis.getDisplayPosition(value.getValue());
thumb.relocate(pixelOnAxis, getHeight() /4);
kleopatra
  • 51,061
  • 28
  • 99
  • 211