Zoomen in JavaFx: ScrollEvent wird verwendet, wenn die Inhaltsgröße das ScrollPane-Ansichtsfenster überschreitet

Ich habe eine Anwendung, die das Zoomen in einem ScrollPane erfordert, aber mit meiner aktuellen Herangehensweise stehe ich immer noch zwei Herausforderungen gegenüber. Um das Problem zu replizieren, habe ich eine kleine Anwendung ZoomApp geschrieben, unter der Sie den Code finden. Seine eingeschränkte Funktionalität ermöglicht das Vergrößern und Verkleinern (mit Strg + Mausrad) einiger beliebiger Formen. Wenn der gezoomte Inhalt außerhalb der Fenstergrenzen wächst, sollten Bildlaufleisten angezeigt werden.

Herausforderung 1. Wenn die Bildlaufleisten als Folge von angezeigt werdeninnerGroup vergrößert sich, das ScrollEvent erreicht meinen ZoomHandler nicht mehr. Stattdessen scrollen wir das Fenster nach unten, bis es den unteren Rand erreicht, wenn das Zoomen wieder wie erwartet funktioniert. Ich dachte vielleicht

scrollPane.setPannable(false);

würde einen Unterschied machen, aber nein. Wie soll dieses unerwünschte Verhalten vermieden werden?

Herausforderung 2. Wie würde ich etwa in die Mitte geheninnerGroup innerhalb des scrollPane, ohne einen Pixel in der oberen linken Ecke von zu maleninnerGroup mit dem gewünschten Delta zu den Quadraten?

Als Randnotiz laut JavaDoc für ScrollPane:"Wenn eine Anwendung möchte, dass der Bildlauf auf den visuellen Grenzen des Knotens basiert (für skalierten Inhalt usw.), muss der Bildlaufknoten in eine Gruppe eingeschlossen werden.". Dies ist der Grund, warum ich eine habeinnerGroup und einäußere Gruppe in ScrollPane.

Alle Vorschläge, die mich zur Lösung führen, werden von diesem JavaFX-Neuling sehr geschätzt.

import javafx.application.Application;
import javafx.event.EventHandler;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.SceneBuilder;
import javafx.scene.control.ScrollPane;
import javafx.scene.input.ScrollEvent;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;

/**
 * Demo of a challenge I have with zooming inside a {@code ScrollPane}.
 * <br>
 * I am running JavaFx 2.2 on a Mac. {@code java -version} yields:
 * <pre>
 * java version "1.7.0_09"
 * Java(TM) SE Runtime Environment (build 1.7.0_09-b05)
 * Java HotSpot(TM) 64-Bit Server VM (build 23.5-b02, mixed mode)
 * </pre>
 * 6 rectangles are drawn, and can be zoomed in and out using either
 * <pre>
 * Ctrl + Mouse Wheel
 * or Ctrl + 2 fingers on the pad.
 * </pre>
 * It reproduces a problem I experience inside an application I am writing.
 * If you magnify to {@link #MAX_SCALE}, an interesting problem occurs when you try to zoom back to {@link #MIN_SCALE}. In the beginning
 * you will see that the {@code scrollPane} scrolls and consumes the {@code ScrollEvent} until we have scrolled to the bottom of the window.
 * Once the bottom of the window is reached, it behaves as expected (or at least as I was expecting).
 *
 * @author Skjalg Bjørndal
 * @since 2012.11.05
 */
public class ZoomApp extends Application {

    private static final int WINDOW_WIDTH = 800;
    private static final int WINDOW_HEIGHT = 600;

    private static final double MAX_SCALE = 2.5d;
    private static final double MIN_SCALE = .5d;

    private class ZoomHandler implements EventHandler<ScrollEvent> {

        private Node nodeToZoom;

        private ZoomHandler(Node nodeToZoom) {
            this.nodeToZoom = nodeToZoom;
        }

        @Override
        public void handle(ScrollEvent scrollEvent) {
            if (scrollEvent.isControlDown()) {
                final double scale = calculateScale(scrollEvent);
                nodeToZoom.setScaleX(scale);
                nodeToZoom.setScaleY(scale);
                scrollEvent.consume();
            }
        }

        private double calculateScale(ScrollEvent scrollEvent) {
            double scale = nodeToZoom.getScaleX() + scrollEvent.getDeltaY() / 100;

            if (scale <= MIN_SCALE) {
                scale = MIN_SCALE;
            } else if (scale >= MAX_SCALE) {
                scale = MAX_SCALE;
            }
            return scale;
        }
    }

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

    @Override
    public void start(Stage stage) throws Exception {

        final Group innerGroup = createSixRectangles();
        final Group outerGroup = new Group(innerGroup);

        final ScrollPane scrollPane = new ScrollPane();
        scrollPane.setContent(outerGroup);
        scrollPane.setOnScroll(new ZoomHandler(innerGroup));

        StackPane stackPane = new StackPane();
        stackPane.getChildren().add(scrollPane);

        Scene scene = SceneBuilder.create()
                .width(WINDOW_WIDTH)
                .height(WINDOW_HEIGHT)
                .root(stackPane)
                .build();

        stage.setScene(scene);
        stage.show();
    }

    private Group createSixRectangles() {
        return new Group(
                createRectangle(0, 0), createRectangle(110, 0), createRectangle(220, 0),
                createRectangle(0, 110), createRectangle(110, 110), createRectangle(220, 110),
                createRectangle(0, 220), createRectangle(110, 220), createRectangle(220, 220)
        );
    }

    private Rectangle createRectangle(int x, int y) {
        Rectangle rectangle = new Rectangle(x, y, 100, 100);
        rectangle.setStroke(Color.ORANGERED);
        rectangle.setFill(Color.ORANGE);
        rectangle.setStrokeWidth(3d);
        return rectangle;
    }
}

Antworten auf die Frage(3)

Ihre Antwort auf die Frage